Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"

This reverts commit 15b7df8ef1.
This commit is contained in:
souldbminersmwc
2025-11-09 16:14:52 -05:00
parent 22ec140738
commit 21a3f953d7
3804 changed files with 435 additions and 570162 deletions

View File

@@ -1,299 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* 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 "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_meta.hpp"
namespace ams::ldr {
namespace {
/* Convenience definitions. */
constexpr size_t MetaCacheBufferSize = 0x8000;
constexpr inline const char AtmosphereMetaPath[] = ENCODE_ATMOSPHERE_CODE_PATH("/main.npdm");
constexpr inline const char SdOrBaseMetaPath[] = ENCODE_SD_OR_CODE_PATH("/main.npdm");
constexpr inline const char BaseMetaPath[] = ENCODE_CODE_PATH("/main.npdm");
/* Types. */
struct MetaCache {
Meta meta;
u8 buffer[MetaCacheBufferSize];
};
/* Global storage. */
ncm::ProgramId g_cached_program_id;
cfg::OverrideStatus g_cached_override_status;
MetaCache g_meta_cache;
MetaCache g_original_meta_cache;
/* Helpers. */
Result ValidateSubregion(size_t allowed_start, size_t allowed_end, size_t start, size_t size, size_t min_size = 0) {
R_UNLESS(size >= min_size, ldr::ResultInvalidMeta());
R_UNLESS(allowed_start <= start, ldr::ResultInvalidMeta());
R_UNLESS(start <= allowed_end, ldr::ResultInvalidMeta());
R_UNLESS(start + size <= allowed_end, ldr::ResultInvalidMeta());
R_SUCCEED();
}
Result ValidateNpdm(const Npdm *npdm, size_t size) {
/* Validate magic. */
R_UNLESS(npdm->magic == Npdm::Magic, ldr::ResultInvalidMeta());
/* Validate flags. */
constexpr u32 InvalidMetaFlagMask = 0x80000000;
R_UNLESS(!(npdm->flags & InvalidMetaFlagMask), ldr::ResultInvalidMeta());
/* Validate Acid extents. */
R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->acid_offset, npdm->acid_size, sizeof(Acid)));
/* Validate Aci extends. */
R_TRY(ValidateSubregion(sizeof(Npdm), size, npdm->aci_offset, npdm->aci_size, sizeof(Aci)));
R_SUCCEED();
}
Result ValidateAcid(const Acid *acid, size_t size) {
/* Validate magic. */
R_UNLESS(acid->magic == Acid::Magic, ldr::ResultInvalidMeta());
/* Validate that the acid is for production if not development. */
if (!IsDevelopmentForAcidProductionCheck()) {
R_UNLESS((acid->flags & Acid::AcidFlag_Production) != 0, ldr::ResultInvalidMeta());
}
/* Validate that the acid version is correct. */
constexpr u8 SupportedSdkMajorVersion = ams::svc::ConvertToSdkMajorVersion(ams::svc::SupportedKernelMajorVersion);
if (acid->unknown_209 < SupportedSdkMajorVersion) {
R_UNLESS(acid->version == 0, ldr::ResultInvalidMeta());
R_UNLESS(acid->unknown_209 == 0, ldr::ResultInvalidMeta());
}
/* Validate Fac, Sac, Kac. */
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->fac_offset, acid->fac_size));
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->sac_offset, acid->sac_size));
R_TRY(ValidateSubregion(sizeof(Acid), size, acid->kac_offset, acid->kac_size));
R_SUCCEED();
}
Result ValidateAci(const Aci *aci, size_t size) {
/* Validate magic. */
R_UNLESS(aci->magic == Aci::Magic, ldr::ResultInvalidMeta());
/* Validate Fah, Sac, Kac. */
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->fah_offset, aci->fah_size));
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->sac_offset, aci->sac_size));
R_TRY(ValidateSubregion(sizeof(Aci), size, aci->kac_offset, aci->kac_size));
R_SUCCEED();
}
const u8 *GetAcidSignatureModulus(ncm::ContentMetaPlatform platform, u8 key_generation, bool unk_unused) {
return fssystem::GetAcidSignatureKeyModulus(platform, !IsDevelopmentForAcidSignatureCheck(), key_generation, unk_unused);
}
size_t GetAcidSignatureModulusSize(ncm::ContentMetaPlatform platform, bool unk_unused) {
return fssystem::GetAcidSignatureKeyModulusSize(platform, unk_unused);
}
Result ValidateAcidSignature(Meta *meta, ncm::ContentMetaPlatform platform, bool unk_unused) {
R_SUCCEED();
/* Loader did not check signatures prior to 10.0.0. */
if (hos::GetVersion() < hos::Version_10_0_0) {
meta->check_verification_data = false;
R_SUCCEED();
}
/* Get the signature key generation. */
const auto signature_key_generation = meta->npdm->signature_key_generation;
R_UNLESS(fssystem::IsValidSignatureKeyGeneration(platform, signature_key_generation), ldr::ResultInvalidMeta());
/* Verify the signature. */
const u8 *sig = meta->acid->signature;
const size_t sig_size = sizeof(meta->acid->signature);
const u8 *mod = GetAcidSignatureModulus(platform, signature_key_generation, unk_unused);
const size_t mod_size = GetAcidSignatureModulusSize(platform, unk_unused);
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *msg = meta->acid->modulus;
const size_t msg_size = meta->acid->size;
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256(sig, sig_size, mod, mod_size, exp, exp_size, msg, msg_size);
R_UNLESS(is_signature_valid || !IsEnabledProgramVerification(), ldr::ResultInvalidAcidSignature());
meta->check_verification_data = is_signature_valid;
R_SUCCEED();
}
Result LoadMetaFromFile(fs::FileHandle file, MetaCache *cache) {
/* Reset cache. */
cache->meta = {};
/* Read from file. */
s64 npdm_size = 0;
{
/* Get file size. */
R_TRY(fs::GetFileSize(std::addressof(npdm_size), file));
/* Read data into cache buffer. */
R_UNLESS(npdm_size <= static_cast<s64>(MetaCacheBufferSize), ldr::ResultMetaOverflow());
R_TRY(fs::ReadFile(file, 0, cache->buffer, npdm_size));
}
/* Ensure size is big enough. */
R_UNLESS(npdm_size >= static_cast<s64>(sizeof(Npdm)), ldr::ResultInvalidMeta());
/* Validate the meta. */
{
Meta *meta = std::addressof(cache->meta);
Npdm *npdm = reinterpret_cast<Npdm *>(cache->buffer);
R_TRY(ValidateNpdm(npdm, npdm_size));
Acid *acid = reinterpret_cast<Acid *>(cache->buffer + npdm->acid_offset);
Aci *aci = reinterpret_cast<Aci *>(cache->buffer + npdm->aci_offset);
R_TRY(ValidateAcid(acid, npdm->acid_size));
R_TRY(ValidateAci(aci, npdm->aci_size));
/* Set Meta members. */
meta->npdm = npdm;
meta->acid = acid;
meta->aci = aci;
meta->acid_fac = reinterpret_cast<u8 *>(acid) + acid->fac_offset;
meta->acid_sac = reinterpret_cast<u8 *>(acid) + acid->sac_offset;
meta->acid_kac = reinterpret_cast<u8 *>(acid) + acid->kac_offset;
meta->aci_fah = reinterpret_cast<u8 *>(aci) + aci->fah_offset;
meta->aci_sac = reinterpret_cast<u8 *>(aci) + aci->sac_offset;
meta->aci_kac = reinterpret_cast<u8 *>(aci) + aci->kac_offset;
meta->modulus = acid->modulus;
}
R_SUCCEED();
}
}
/* API. */
Result LoadMeta(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform, bool unk_unused) {
/* Set the cached program id back to zero. */
g_cached_program_id = {};
/* Try to load meta from file. */
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), AtmosphereMetaPath, fs::OpenMode_Read));
{
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadMetaFromFile(file, std::addressof(g_meta_cache)));
}
/* Patch meta. Start by setting all program ids to the current program id. */
Meta *meta = std::addressof(g_meta_cache.meta);
meta->acid->program_id_min = loc.program_id;
meta->acid->program_id_max = loc.program_id;
meta->aci->program_id = loc.program_id;
/* For HBL, we need to copy some information from the base meta. */
if (status.IsHbl()) {
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), SdOrBaseMetaPath, fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
if (R_SUCCEEDED(LoadMetaFromFile(file, std::addressof(g_original_meta_cache)))) {
Meta *o_meta = std::addressof(g_original_meta_cache.meta);
/* Fix pool partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
meta->acid->flags = (meta->acid->flags & 0xFFFFFFC3) | (o_meta->acid->flags & 0x0000003C);
}
/* Fix flags. */
const u16 program_info_flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(o_meta->aci_kac), o_meta->aci->kac_size / sizeof(util::BitPack32));
UpdateProgramInfoFlag(program_info_flags, static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32));
UpdateProgramInfoFlag(program_info_flags, static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
}
}
/* Perform address space override. */
if (status.HasOverrideAddressSpace()) {
/* Clear the existing address space. */
meta->npdm->flags &= ~Npdm::MetaFlag_AddressSpaceTypeMask;
/* Set the new address space flag. */
switch (status.GetOverrideAddressSpaceFlags()) {
case cfg::impl::OverrideStatusFlag_AddressSpace32Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_32Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
case cfg::impl::OverrideStatusFlag_AddressSpace64BitDeprecated: meta->npdm->flags |= (Npdm::AddressSpaceType_64BitDeprecated) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
case cfg::impl::OverrideStatusFlag_AddressSpace32BitWithoutAlias: meta->npdm->flags |= (Npdm::AddressSpaceType_32BitWithoutAlias) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
case cfg::impl::OverrideStatusFlag_AddressSpace64Bit: meta->npdm->flags |= (Npdm::AddressSpaceType_64Bit) << Npdm::MetaFlag_AddressSpaceTypeShift; break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
}
/* When hbl is applet, adjust main thread priority. */
if ((MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet) {
constexpr auto HblMainThreadPriorityApplication = 44;
constexpr auto HblMainThreadPriorityApplet = 40;
if (meta->npdm->main_thread_priority == HblMainThreadPriorityApplication) {
meta->npdm->main_thread_priority = HblMainThreadPriorityApplet;
}
}
/* Fix the debug capabilities, to prevent needing a hbl recompilation. */
FixDebugCapabilityForHbl(static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32));
FixDebugCapabilityForHbl(static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
} else if (hos::GetVersion() >= hos::Version_10_0_0) {
/* If storage id is none, there is no base code filesystem, and thus it is impossible for us to validate. */
/* However, if we're an application, we are guaranteed a base code filesystem. */
if (static_cast<ncm::StorageId>(loc.storage_id) != ncm::StorageId::None || ncm::IsApplicationId(loc.program_id)) {
R_TRY(fs::OpenFile(std::addressof(file), BaseMetaPath, fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadMetaFromFile(file, std::addressof(g_original_meta_cache)));
R_TRY(ValidateAcidSignature(std::addressof(g_original_meta_cache.meta), platform, unk_unused));
meta->modulus = g_original_meta_cache.meta.modulus;
meta->check_verification_data = g_original_meta_cache.meta.check_verification_data;
}
}
/* Pre-process the capabilities. */
/* This is used to e.g. avoid passing memory region descriptor to older kernels. */
PreProcessCapability(static_cast<util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32));
PreProcessCapability(static_cast<util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
/* Set output. */
g_cached_program_id = loc.program_id;
g_cached_override_status = status;
*out_meta = *meta;
R_SUCCEED();
}
Result LoadMetaFromCache(Meta *out_meta, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &status, ncm::ContentMetaPlatform platform) {
if (g_cached_program_id != loc.program_id || g_cached_override_status != status) {
R_RETURN(LoadMeta(out_meta, loc, status, platform, false));
}
*out_meta = g_meta_cache.meta;
R_SUCCEED();
}
void InvalidateMetaCache() {
/* Set the cached program id back to zero. */
g_cached_program_id = {};
}
}

View File

@@ -1,780 +0,0 @@
/*
* Copyright (c) Atmosphère-NX
*
* 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 "ldr_capabilities.hpp"
#include "ldr_content_management.hpp"
#include "ldr_development_manager.hpp"
#include "ldr_launch_record.hpp"
#include "ldr_meta.hpp"
#include "ldr_patcher.hpp"
#include "ldr_process_creation.hpp"
#include "ldr_ro_manager.hpp"
#include "ldr_ro_manager.hpp"
#include "oc/oc_loader.hpp"
namespace ams::ldr {
namespace {
/* Convenience defines. */
constexpr size_t SystemResourceSizeMax = 0x1FE00000;
/* Types. */
enum NsoIndex {
Nso_Rtld = 0,
Nso_Main = 1,
Nso_Compat0 = 2,
Nso_Compat1 = 3,
Nso_Compat2 = 4,
Nso_Compat3 = 5,
Nso_Compat4 = 6,
Nso_Compat5 = 7,
Nso_Compat6 = 8,
Nso_Compat7 = 9,
Nso_Compat8 = 10,
Nso_Compat9 = 11,
Nso_SubSdk0 = 12,
Nso_SubSdk1 = 13,
Nso_SubSdk2 = 14,
Nso_SubSdk3 = 15,
Nso_SubSdk4 = 16,
Nso_SubSdk5 = 17,
Nso_SubSdk6 = 18,
Nso_SubSdk7 = 19,
Nso_SubSdk8 = 20,
Nso_SubSdk9 = 21,
Nso_Sdk = 22,
Nso_Count,
};
constexpr inline const char *NsoPaths[Nso_Count] = {
ENCODE_ATMOSPHERE_CODE_PATH("/rtld"),
ENCODE_ATMOSPHERE_CODE_PATH("/main"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat0"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat1"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat2"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat3"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat4"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat5"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat6"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat7"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat8"),
ENCODE_ATMOSPHERE_CMPT_PATH("/compat9"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk0"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk1"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk2"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk3"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk4"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk5"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk6"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk7"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk8"),
ENCODE_ATMOSPHERE_CODE_PATH("/subsdk9"),
ENCODE_ATMOSPHERE_CODE_PATH("/sdk"),
};
constexpr const char *GetNsoPath(size_t idx) {
AMS_ABORT_UNLESS(idx < Nso_Count);
return NsoPaths[idx];
}
struct ProcessInfo {
os::NativeHandle process_handle;
uintptr_t args_address;
size_t args_size;
uintptr_t nso_address[Nso_Count];
size_t nso_size[Nso_Count];
};
/* Global NSO header cache. */
bool g_has_nso[Nso_Count];
NsoHeader g_nso_headers[Nso_Count];
/* Pcv/Ptm check cache */
bool g_is_pcv;
bool g_is_ptm;
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {
/* No version verification is done before 8.1.0. */
R_SUCCEED_IF(hos::GetVersion() < hos::Version_8_1_0);
/* No verification is done if development. */
R_SUCCEED_IF(IsDevelopmentForAntiDowngradeCheck());
/* TODO: Anti-downgrade checking does not make very much sense for us. Should we do anything? */
AMS_UNUSED(program_id, version);
R_SUCCEED();
}
/* Helpers. */
Result GetProgramInfoFromMeta(ProgramInfo *out, const Meta *meta) {
/* Copy basic info. */
out->main_thread_priority = meta->npdm->main_thread_priority;
out->default_cpu_id = meta->npdm->default_cpu_id;
out->main_thread_stack_size = meta->npdm->main_thread_stack_size;
out->program_id = meta->aci->program_id;
/* Copy access controls. */
size_t offset = 0;
#define COPY_ACCESS_CONTROL(source, which) \
({ \
const size_t size = meta->source->which##_size; \
R_UNLESS(offset + size <= sizeof(out->ac_buffer), ldr::ResultInternalError()); \
out->source##_##which##_size = size; \
std::memcpy(out->ac_buffer + offset, meta->source##_##which, size); \
offset += size; \
})
/* Copy all access controls to buffer. */
COPY_ACCESS_CONTROL(acid, sac);
COPY_ACCESS_CONTROL(aci, sac);
COPY_ACCESS_CONTROL(acid, fac);
COPY_ACCESS_CONTROL(aci, fah);
#undef COPY_ACCESS_CONTROL
/* Copy flags. */
out->flags = MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32));
R_SUCCEED();
}
bool IsApplet(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Applet;
}
bool IsApplication(const Meta *meta) {
return (MakeProgramInfoFlag(static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)) & ProgramInfoFlag_ApplicationTypeMask) == ProgramInfoFlag_Application;
}
Npdm::AddressSpaceType GetAddressSpaceType(const Meta *meta) {
return static_cast<Npdm::AddressSpaceType>((meta->npdm->flags & Npdm::MetaFlag_AddressSpaceTypeMask) >> Npdm::MetaFlag_AddressSpaceTypeShift);
}
Acid::PoolPartition GetPoolPartition(const Meta *meta) {
return static_cast<Acid::PoolPartition>((meta->acid->flags & Acid::AcidFlag_PoolPartitionMask) >> Acid::AcidFlag_PoolPartitionShift);
}
Result LoadAutoLoadHeaders(NsoHeader *nso_headers, bool *has_nso) {
/* Clear NSOs. */
std::memset(nso_headers, 0, sizeof(*nso_headers) * Nso_Count);
std::memset(has_nso, 0, sizeof(*has_nso) * Nso_Count);
for (size_t i = 0; i < Nso_Count; i++) {
fs::FileHandle file;
if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read))) {
ON_SCOPE_EXIT { fs::CloseFile(file); };
/* Read NSO header. */
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, 0, nso_headers + i, sizeof(*nso_headers)));
R_UNLESS(read_size == sizeof(*nso_headers), ldr::ResultInvalidNso());
has_nso[i] = true;
}
}
R_SUCCEED();
}
Result CheckAutoLoad(const NsoHeader *nso_headers, const bool *has_nso) {
/* We must always have a main. */
R_UNLESS(has_nso[Nso_Main], ldr::ResultInvalidNso());
/* If we don't have an RTLD, we must only have a main. */
if (!has_nso[Nso_Rtld]) {
for (size_t i = Nso_Main + 1; i < Nso_Count; i++) {
R_UNLESS(!has_nso[i], ldr::ResultInvalidNso());
}
}
/* All NSOs must have zero text offset. */
for (size_t i = 0; i < Nso_Count; i++) {
R_UNLESS(nso_headers[i].text_dst_offset == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
constexpr const ncm::ProgramId UnqualifiedApprovalProgramIds[] = {
{ 0x010003F003A34000 }, /* Pokemon: Let's Go, Pikachu! */
{ 0x0100152000022000 }, /* Mario Kart 8 Deluxe */
{ 0x0100165003504000 }, /* Nintendo Labo Toy-Con 04: VR Kit */
{ 0x0100187003A36000 }, /* Pokemon: Let's Go, Eevee! */
{ 0x01002E5008C56000 }, /* Pokemon Sword [Live Tournament] */
{ 0x01002FF008C24000 }, /* Ring Fit Adventure */
{ 0x010049900F546001 }, /* Super Mario 3D All-Stars: Super Mario 64 */
{ 0x010057D00ECE4000 }, /* Nintendo Switch Online (Nintendo 64) [for Japan] */
{ 0x01006F8002326000 }, /* Animal Crossing: New Horizons */
{ 0x01006FB00F50E000 }, /* [???] */
{ 0x010070300F50C000 }, /* [???] */
{ 0x010075100E8EC000 }, /* 马力欧卡丁车8 豪华版 [Mario Kart 8 Deluxe for China] */
{ 0x01008DB008C2C000 }, /* Pokemon Shield */
{ 0x01009AD008C4C000 }, /* Pokemon: Let's Go, Pikachu! [Kiosk] */
{ 0x0100A66003384000 }, /* Hulu */
{ 0x0100ABF008968000 }, /* Pokemon Sword */
{ 0x0100C9A00ECE6000 }, /* Nintendo Switch Online (Nintendo 64) [for America] */
{ 0x0100ED100BA3A000 }, /* Mario Kart Live: Home Circuit */
{ 0x0100F38011CFE000 }, /* Animal Crossing: New Horizons Island Transfer Tool */
{ 0x0100F6B011028000 }, /* 健身环大冒险 [Ring Fit Adventure for China] */
};
/* Check that the unqualified approval programs are sorted. */
static_assert([]() -> bool {
for (size_t i = 0; i < util::size(UnqualifiedApprovalProgramIds) - 1; ++i) {
if (UnqualifiedApprovalProgramIds[i].value >= UnqualifiedApprovalProgramIds[i + 1].value) {
return false;
}
}
return true;
}());
bool IsUnqualifiedApprovalProgramId(ncm::ProgramId program_id) {
/* Check if the program id is one with unqualified approval. */
return std::binary_search(std::begin(UnqualifiedApprovalProgramIds), std::end(UnqualifiedApprovalProgramIds), program_id);
}
bool IsUnqualifiedApproval(const Meta *meta) {
/* If the meta has unqualified approval flag, it's unqualified approval. */
if (meta->acid->flags & ldr::Acid::AcidFlag_UnqualifiedApproval) {
return true;
}
/* If the unqualified approval flag is not set, the program must be an application. */
if (!IsApplication(meta)) {
return false;
}
/* The program id must be a force unqualified approval program id. */
return IsUnqualifiedApprovalProgramId(meta->acid->program_id_min) && meta->acid->program_id_min == meta->acid->program_id_max;
}
Result ValidateMeta(const Meta *meta, const ncm::ProgramLocation &loc, const fs::CodeVerificationData &code_verification_data) {
/* Validate version. */
R_TRY(ValidateProgramVersion(loc.program_id, meta->npdm->version));
/* Validate program id. */
R_UNLESS(meta->aci->program_id >= meta->acid->program_id_min, ldr::ResultInvalidProgramId());
R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Validate the kernel capabilities. */
R_TRY(TestCapability(static_cast<const util::BitPack32 *>(meta->acid_kac), meta->acid->kac_size / sizeof(util::BitPack32), static_cast<const util::BitPack32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(util::BitPack32)));
/* Check if NCA is PCV or PTM */
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
/* If we have data to validate, validate it. */
if (meta->check_verification_data) {
const u8 *sig = code_verification_data.signature;
const size_t sig_size = sizeof(code_verification_data.signature);
const u8 *mod = static_cast<u8 *>(meta->modulus);
const size_t mod_size = crypto::Rsa2048PssSha256Verifier::ModulusSize;
const u8 *exp = fssystem::GetAcidSignatureKeyPublicExponent();
const size_t exp_size = fssystem::AcidSignatureKeyPublicExponentSize;
const u8 *hsh = code_verification_data.target_hash;
const size_t hsh_size = sizeof(code_verification_data.target_hash);
const bool is_signature_valid = crypto::VerifyRsa2048PssSha256WithHash(sig, sig_size, mod, mod_size, exp, exp_size, hsh, hsh_size);
/* If the signature check fails, we need to check if this is allowable. */
if (!is_signature_valid) {
/* We have to enforce signature checks on prod and when we have a signature to check on dev. */
R_UNLESS(IsDevelopmentForAcidProductionCheck(), ldr::ResultInvalidNcaSignature());
R_UNLESS(!code_verification_data.has_data, ldr::ResultInvalidNcaSignature());
/* There was no signature to check on dev. Check if this is acceptable. */
R_UNLESS(IsUnqualifiedApproval(meta), ldr::ResultInvalidNcaSignature());
}
}
/* All good. */
R_SUCCEED();
}
Result GetCreateProcessFlags(u32 *out, const Meta *meta, const u32 ldr_flags) {
const u8 meta_flags = meta->npdm->flags;
u32 flags = 0;
/* Set Is64Bit. */
if (meta_flags & Npdm::MetaFlag_Is64Bit) {
flags |= svc::CreateProcessFlag_Is64Bit;
}
/* Set AddressSpaceType. */
switch (GetAddressSpaceType(meta)) {
case Npdm::AddressSpaceType_32Bit:
flags |= svc::CreateProcessFlag_AddressSpace32Bit;
break;
case Npdm::AddressSpaceType_64BitDeprecated:
flags |= svc::CreateProcessFlag_AddressSpace64BitDeprecated;
break;
case Npdm::AddressSpaceType_32BitWithoutAlias:
flags |= svc::CreateProcessFlag_AddressSpace32BitWithoutAlias;
break;
case Npdm::AddressSpaceType_64Bit:
flags |= svc::CreateProcessFlag_AddressSpace64Bit;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
/* Set Enable Debug. */
if (ldr_flags & CreateProcessFlag_EnableDebug) {
flags |= svc::CreateProcessFlag_EnableDebug;
}
/* Set Enable ASLR. */
if (!(ldr_flags & CreateProcessFlag_DisableAslr)) {
flags |= svc::CreateProcessFlag_EnableAslr;
}
/* Set Is Application. */
if (IsApplication(meta)) {
flags |= svc::CreateProcessFlag_IsApplication;
/* 7.0.0+: Set OptimizeMemoryAllocation if relevant. */
if (hos::GetVersion() >= hos::Version_7_0_0) {
if (meta_flags & Npdm::MetaFlag_OptimizeMemoryAllocation) {
flags |= svc::CreateProcessFlag_OptimizeMemoryAllocation;
}
}
}
/* 5.0.0+ Set Pool Partition. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
/* TODO: Nintendo no longer accepts Applet when pool partition == application. Would this break hbl/anything else in the hb ecosystem? */
/* TODO: Nintendo uses a helper bool MakeSvcPoolPartitionFlag(u32 *out, Acid::PoolPartition partition); */
switch (GetPoolPartition(meta)) {
case Acid::PoolPartition_Application:
if (IsApplet(meta)) {
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
} else {
flags |= svc::CreateProcessFlag_PoolPartitionApplication;
}
break;
case Acid::PoolPartition_Applet:
flags |= svc::CreateProcessFlag_PoolPartitionApplet;
break;
case Acid::PoolPartition_System:
flags |= svc::CreateProcessFlag_PoolPartitionSystem;
break;
case Acid::PoolPartition_SystemNonSecure:
flags |= svc::CreateProcessFlag_PoolPartitionSystemNonSecure;
break;
default:
R_THROW(ldr::ResultInvalidMeta());
}
} else if (hos::GetVersion() >= hos::Version_4_0_0) {
/* On 4.0.0+, the corresponding bit was simply "UseSecureMemory". */
if (meta->acid->flags & Acid::AcidFlag_DeprecatedUseSecureMemory) {
flags |= svc::CreateProcessFlag_DeprecatedUseSecureMemory;
}
}
/* 11.0.0+/meso Set Disable DAS merge. */
if (meta_flags & Npdm::MetaFlag_DisableDeviceAddressSpaceMerge) {
flags |= svc::CreateProcessFlag_DisableDeviceAddressSpaceMerge;
}
/* 18.0.0+/meso Set Alias region extra size. */
if (meta_flags & Npdm::MetaFlag_EnableAliasRegionExtraSize) {
flags |= svc::CreateProcessFlag_EnableAliasRegionExtraSize;
}
*out = flags;
R_SUCCEED();
}
Result GetCreateProcessParameter(svc::CreateProcessParameter *out, const Meta *meta, u32 flags, os::NativeHandle resource_limit) {
/* Clear output. */
std::memset(out, 0, sizeof(*out));
/* Set name, version, program id, resource limit handle. */
std::memcpy(out->name, meta->npdm->program_name, sizeof(out->name) - 1);
out->version = meta->npdm->version;
out->program_id = meta->aci->program_id.value;
out->reslimit = resource_limit;
/* Set flags. */
R_TRY(GetCreateProcessFlags(std::addressof(out->flags), meta, flags));
/* 3.0.0+ System Resource Size. */
if (hos::GetVersion() >= hos::Version_3_0_0) {
/* Validate size is aligned. */
R_UNLESS(util::IsAligned(meta->npdm->system_resource_size, os::MemoryBlockUnitSize), ldr::ResultInvalidSize());
/* Validate system resource usage. */
if (meta->npdm->system_resource_size) {
/* Process must be 64-bit. */
R_UNLESS((out->flags & svc::CreateProcessFlag_AddressSpace64Bit), ldr::ResultInvalidMeta());
/* Process must be application or applet. */
R_UNLESS(IsApplication(meta) || IsApplet(meta), ldr::ResultInvalidMeta());
/* Size must be less than or equal to max. */
R_UNLESS(meta->npdm->system_resource_size <= SystemResourceSizeMax, ldr::ResultInvalidMeta());
}
out->system_resource_num_pages = meta->npdm->system_resource_size >> 12;
}
R_SUCCEED();
}
u64 GenerateSecureRandom(u64 max) {
/* Generate a cryptographically random number. */
u64 rand;
crypto::GenerateCryptographicallyRandomBytes(std::addressof(rand), sizeof(rand));
/* Coerce into range. */
return rand % (max + 1);
}
Result DecideAddressSpaceLayout(ProcessInfo *out, svc::CreateProcessParameter *out_param, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument) {
/* Clear output. */
out->args_address = 0;
out->args_size = 0;
std::memset(out->nso_address, 0, sizeof(out->nso_address));
std::memset(out->nso_size, 0, sizeof(out->nso_size));
size_t total_size = 0;
bool argument_allocated = false;
/* Calculate base offsets. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
out->nso_address[i] = total_size;
const size_t text_end = nso_headers[i].text_dst_offset + nso_headers[i].text_size;
const size_t ro_end = nso_headers[i].ro_dst_offset + nso_headers[i].ro_size;
const size_t rw_end = nso_headers[i].rw_dst_offset + nso_headers[i].rw_size + nso_headers[i].bss_size;
out->nso_size[i] = text_end;
out->nso_size[i] = std::max(out->nso_size[i], ro_end);
out->nso_size[i] = std::max(out->nso_size[i], rw_end);
out->nso_size[i] = util::AlignUp(out->nso_size[i], os::MemoryPageSize);
total_size += out->nso_size[i];
if (!argument_allocated && argument != nullptr) {
out->args_address = total_size;
out->args_size = util::AlignUp(2 * sizeof(u32) + argument->argument_size * 2 + ArgumentStore::ArgumentBufferSize, os::MemoryPageSize);
total_size += out->args_size;
argument_allocated = true;
}
}
}
/* Calculate ASLR. */
uintptr_t aslr_start = 0;
size_t aslr_size = 0;
if (hos::GetVersion() >= hos::Version_2_0_0) {
switch (out_param->flags & svc::CreateProcessFlag_AddressSpaceMask) {
case svc::CreateProcessFlag_AddressSpace32Bit:
case svc::CreateProcessFlag_AddressSpace32BitWithoutAlias:
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
break;
case svc::CreateProcessFlag_AddressSpace64BitDeprecated:
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
break;
case svc::CreateProcessFlag_AddressSpace64Bit:
aslr_start = svc::AddressMap39Start;
aslr_size = svc::AddressMap39Size;
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
} else {
/* On 1.0.0, only 2 address space types existed. */
if (out_param->flags & svc::CreateProcessFlag_AddressSpace64BitDeprecated) {
aslr_start = svc::AddressSmallMap36Start;
aslr_size = svc::AddressSmallMap36Size;
} else {
aslr_start = svc::AddressSmallMap32Start;
aslr_size = svc::AddressSmallMap32Size;
}
}
R_UNLESS(total_size <= aslr_size, svc::ResultOutOfMemory());
/* Set Create Process output. */
uintptr_t aslr_slide = 0;
size_t free_size = (aslr_size - total_size);
if (out_param->flags & svc::CreateProcessFlag_EnableAslr) {
aslr_slide = GenerateSecureRandom(free_size / os::MemoryBlockUnitSize) * os::MemoryBlockUnitSize;
}
/* Set out. */
aslr_start += aslr_slide;
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
out->nso_address[i] += aslr_start;
}
}
if (out->args_address) {
out->args_address += aslr_start;
}
out_param->code_address = aslr_start;
out_param->code_num_pages = total_size >> 12;
R_SUCCEED();
}
Result LoadAutoLoadModuleSegment(fs::FileHandle file, const NsoHeader::SegmentInfo *segment, size_t file_size, const u8 *file_hash, bool is_compressed, bool check_hash, uintptr_t map_base, uintptr_t map_end) {
/* Select read size based on compression. */
if (!is_compressed) {
file_size = segment->size;
}
/* Validate size. */
R_UNLESS(file_size <= segment->size, ldr::ResultInvalidNso());
R_UNLESS(segment->size <= std::numeric_limits<s32>::max(), ldr::ResultInvalidNso());
/* Load data from file. */
uintptr_t load_address = is_compressed ? map_end - file_size : map_base;
size_t read_size;
R_TRY(fs::ReadFile(std::addressof(read_size), file, segment->file_offset, reinterpret_cast<void *>(load_address), file_size));
R_UNLESS(read_size == file_size, ldr::ResultInvalidNso());
/* Uncompress if necessary. */
if (is_compressed) {
bool decompressed = (util::DecompressLZ4(reinterpret_cast<void *>(map_base), segment->size, reinterpret_cast<const void *>(load_address), file_size) == static_cast<int>(segment->size));
R_UNLESS(decompressed, ldr::ResultInvalidNso());
}
/* Check hash if necessary. */
if (check_hash) {
u8 hash[crypto::Sha256Generator::HashSize];
crypto::GenerateSha256(hash, sizeof(hash), reinterpret_cast<void *>(map_base), segment->size);
R_UNLESS(std::memcmp(hash, file_hash, sizeof(hash)) == 0, ldr::ResultInvalidNso());
}
R_SUCCEED();
}
Result LoadAutoLoadModule(os::NativeHandle process_handle, fs::FileHandle file, const NsoHeader *nso_header, uintptr_t nso_address, size_t nso_size, bool prevent_code_reads) {
/* Map and read data from file. */
{
/* Map the process memory. */
void *mapped_memory = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(mapped_memory), process_handle, nso_address, nso_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(mapped_memory, process_handle, nso_address, nso_size); };
const uintptr_t map_address = reinterpret_cast<uintptr_t>(mapped_memory);
/* Load NSO segments. */
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Text]), nso_header->text_compressed_size, nso_header->text_hash, (nso_header->flags & NsoHeader::Flag_CompressedText) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashText) != 0, map_address + nso_header->text_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Ro]), nso_header->ro_compressed_size, nso_header->ro_hash, (nso_header->flags & NsoHeader::Flag_CompressedRo) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRo) != 0, map_address + nso_header->ro_dst_offset, map_address + nso_size));
R_TRY(LoadAutoLoadModuleSegment(file, std::addressof(nso_header->segments[NsoHeader::Segment_Rw]), nso_header->rw_compressed_size, nso_header->rw_hash, (nso_header->flags & NsoHeader::Flag_CompressedRw) != 0,
(nso_header->flags & NsoHeader::Flag_CheckHashRw) != 0, map_address + nso_header->rw_dst_offset, map_address + nso_size));
/* Clear unused space to zero. */
const size_t text_end = nso_header->text_dst_offset + nso_header->text_size;
const size_t ro_end = nso_header->ro_dst_offset + nso_header->ro_size;
const size_t rw_end = nso_header->rw_dst_offset + nso_header->rw_size;
std::memset(reinterpret_cast<void *>(map_address + 0), 0, nso_header->text_dst_offset);
std::memset(reinterpret_cast<void *>(map_address + text_end), 0, nso_header->ro_dst_offset - text_end);
std::memset(reinterpret_cast<void *>(map_address + ro_end), 0, nso_header->rw_dst_offset - ro_end);
std::memset(reinterpret_cast<void *>(map_address + rw_end), 0, nso_header->bss_size);
/* Apply embedded patches. */
ApplyEmbeddedPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply IPS patches. */
LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply PCV and PTM patches */
if (g_is_pcv)
oc::pcv::Patch(map_address, nso_size);
if (g_is_ptm)
oc::ptm::Patch(map_address, nso_size);
}
/* 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);
if (text_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->text_dst_offset, text_size, prevent_code_reads ? os::MemoryPermission_ExecuteOnly : os::MemoryPermission_ReadExecute));
}
if (ro_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->ro_dst_offset, ro_size, os::MemoryPermission_ReadOnly));
}
if (rw_size) {
R_TRY(os::SetProcessMemoryPermission(process_handle, nso_address + nso_header->rw_dst_offset, rw_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result LoadAutoLoadModules(const ProcessInfo *process_info, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, bool prevent_code_reads) {
/* Load each NSO. */
for (size_t i = 0; i < Nso_Count; i++) {
if (has_nso[i]) {
fs::FileHandle file;
R_TRY(fs::OpenFile(std::addressof(file), GetNsoPath(i), fs::OpenMode_Read));
ON_SCOPE_EXIT { fs::CloseFile(file); };
R_TRY(LoadAutoLoadModule(process_info->process_handle, file, nso_headers + i, process_info->nso_address[i], process_info->nso_size[i], prevent_code_reads));
}
}
/* Load arguments, if present. */
if (argument != nullptr) {
/* Write argument data into memory. */
{
void *map_address = nullptr;
R_TRY(os::MapProcessMemory(std::addressof(map_address), process_info->process_handle, process_info->args_address, process_info->args_size, GenerateSecureRandom));
ON_SCOPE_EXIT { os::UnmapProcessMemory(map_address, process_info->process_handle, process_info->args_address, process_info->args_size); };
ProgramArguments *args = static_cast<ProgramArguments *>(map_address);
std::memset(args, 0, sizeof(*args));
args->allocated_size = process_info->args_size;
args->arguments_size = argument->argument_size;
std::memcpy(args->arguments, argument->argument, argument->argument_size);
}
/* Set argument region permissions. */
/* NOTE: Nintendo uses svc::SetProcessMemoryPermission directly here. */
R_TRY(os::SetProcessMemoryPermission(process_info->process_handle, process_info->args_address, process_info->args_size, os::MemoryPermission_ReadWrite));
}
R_SUCCEED();
}
Result CreateProcessAndLoadAutoLoadModules(ProcessInfo *out, const Meta *meta, const NsoHeader *nso_headers, const bool *has_nso, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit) {
/* Get CreateProcessParameter. */
svc::CreateProcessParameter param;
R_TRY(GetCreateProcessParameter(std::addressof(param), meta, flags, resource_limit));
/* Decide on an NSO layout. */
R_TRY(DecideAddressSpaceLayout(out, std::addressof(param), nso_headers, has_nso, argument));
/* Actually create process. */
svc::Handle process_handle;
R_TRY(svc::CreateProcess(std::addressof(process_handle), std::addressof(param), static_cast<const u32 *>(meta->aci_kac), meta->aci->kac_size / sizeof(u32)));
/* Set the output handle, and ensure that if we fail after this point we clean it up. */
out->process_handle = process_handle;
ON_RESULT_FAILURE { svc::CloseHandle(process_handle); };
/* Load all auto load modules. */
R_RETURN(LoadAutoLoadModules(out, nso_headers, has_nso, argument, (meta->npdm->flags & ldr::Npdm::MetaFlag_PreventCodeReads) != 0));
}
}
/* Process Creation API. */
Result CreateProcess(os::NativeHandle *out, PinId pin_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status, const char *path, const ArgumentStore::Entry *argument, u32 flags, os::NativeHandle resource_limit, const ldr::ProgramAttributes &attrs) {
/* Mount code. */
AMS_UNUSED(path);
ScopedCodeMount mount(loc, override_status, attrs);
R_TRY(mount.GetResult());
/* Load meta, possibly from cache. */
Meta meta;
R_TRY(LoadMetaFromCache(std::addressof(meta), loc, override_status, attrs.platform));
/* Validate meta. */
R_TRY(ValidateMeta(std::addressof(meta), loc, mount.GetCodeVerificationData()));
/* Load, validate NSO headers. */
R_TRY(LoadAutoLoadHeaders(g_nso_headers, g_has_nso));
R_TRY(CheckAutoLoad(g_nso_headers, g_has_nso));
/* Actually create the process and load NSOs into process memory. */
ProcessInfo info;
R_TRY(CreateProcessAndLoadAutoLoadModules(std::addressof(info), std::addressof(meta), g_nso_headers, g_has_nso, argument, flags, resource_limit));
/* Register NSOs with the RoManager. */
{
/* Nintendo doesn't validate this get, but we do. */
os::ProcessId process_id = os::GetProcessId(info.process_handle);
/* Register new process. */
const auto as_type = GetAddressSpaceType(std::addressof(meta));
RoManager::GetInstance().RegisterProcess(pin_id, process_id, meta.aci->program_id, as_type == Npdm::AddressSpaceType_64Bit || as_type == Npdm::AddressSpaceType_64BitDeprecated);
/* Register all NSOs. */
for (size_t i = 0; i < Nso_Count; i++) {
if (g_has_nso[i]) {
RoManager::GetInstance().AddNso(pin_id, g_nso_headers[i].module_id, info.nso_address[i], info.nso_size[i]);
}
}
}
/* If we're overriding for HBL, perform HTML document redirection. */
if (override_status.IsHbl()) {
/* Don't validate result, failure is okay. */
RedirectHtmlDocumentPathForHbl(loc);
}
/* Clear the external code for the program. */
fssystem::DestroyExternalCode(loc.program_id);
/* Note that we've created the program. */
SetLaunchedBootProgram(loc.program_id);
/* Move the process handle to output. */
*out = info.process_handle;
R_SUCCEED();
}
Result GetProgramInfo(ProgramInfo *out, cfg::OverrideStatus *out_status, const ncm::ProgramLocation &loc, const char *path, const ldr::ProgramAttributes &attrs) {
Meta meta;
/* Load Meta. */
{
AMS_UNUSED(path);
ScopedCodeMount mount(loc, attrs);
R_TRY(mount.GetResult());
R_TRY(LoadMeta(std::addressof(meta), loc, mount.GetOverrideStatus(), attrs.platform, false));
if (out_status != nullptr) {
*out_status = mount.GetOverrideStatus();
}
}
return GetProgramInfoFromMeta(out, std::addressof(meta));
}
Result PinProgram(PinId *out_id, const ncm::ProgramLocation &loc, const cfg::OverrideStatus &override_status) {
R_UNLESS(RoManager::GetInstance().Allocate(out_id, loc, override_status), ldr::ResultMaxProcess());
R_SUCCEED();
}
Result UnpinProgram(PinId id) {
R_UNLESS(RoManager::GetInstance().Free(id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProcessModuleInfo(u32 *out_count, ldr::ModuleInfo *out, size_t max_out_count, os::ProcessId process_id) {
R_UNLESS(RoManager::GetInstance().GetProcessModuleInfo(out_count, out, max_out_count, process_id), ldr::ResultNotPinned());
R_SUCCEED();
}
Result GetProgramLocationAndOverrideStatusFromPinId(ncm::ProgramLocation *out, cfg::OverrideStatus *out_status, PinId pin_id) {
R_UNLESS(RoManager::GetInstance().GetProgramLocationAndStatus(out, out_status, pin_id), ldr::ResultNotPinned());
R_SUCCEED();
}
}

View File

@@ -1,51 +0,0 @@
TARGET_EXEC := test
BUILD_DIR := ./build
SRC_DIRS := ./
# CXX := clang++ g++-12
# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')
# String substitution for every C/C++ file.
# As an example, hello.cpp turns into ./build/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)
# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))
CPPFLAGS := $(INC_FLAGS) -Wall -Werror -Wno-unused-result -std=c++20 -Og -g
# The final build step.
$(TARGET_EXEC): $(OBJS)
@echo "Linking $@"
@$(CXX) $(OBJS) -o $@ $(LDFLAGS)
# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
@mkdir -p $(dir $@)
@echo "$<"
@$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
@mkdir -p $(dir $@)
@echo "$<"
@$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
.PHONY: clean
clean:
@rm -r $(BUILD_DIR) $(TARGET_EXEC)
# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS)

View File

@@ -1,397 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 "customize.hpp"
#define ENABLED 1
#define DISABLED 0
#define CPU_MAX_MAX_VOLT 1375000
namespace ams::ldr::oc {
//volatile EristaMtcTable EristaMtcTablePlaceholder = { .rev = ERISTA_MTC_MAGIC, };
//volatile MarikoMtcTable MarikoMtcTablePlaceholder = { .rev = MARIKO_MTC_MAGIC, };
volatile CustomizeTable C = {
.commonCpuBoostClock = 1785000, // Default boost clock
.commonEmcMemVolt = 1175000, // LPDDR4X JEDEC Specification
.eristaCpuMaxVolt = 1235,
.eristaEmcMaxClock = 1600000, // Maximum HB-MGCH ram rating
.marikoCpuMaxVolt = 1120,
.marikoEmcMaxClock = 1862400, // Hynix NME and Samsung AM-MGCJ Rating (others are 4766MT, 2133MHz)
.marikoEmcVddqVolt = 600000,
.marikoCpuUV = 0, // No undervolt
.marikoGpuUV = 0,
.eristaCpuUV = 0,
.eristaGpuUV = 0,
.commonGpuVoltOffset = 0,
.EmcDvbShift = 0,
// Defaults - Zeroed
// Primary
.t1_tRCD = 0,
.t2_tRP = 0,
.t3_tRAS = 0,
// Secondary
.t4_tRRD = 0,
.t5_tRFC = 0,
.t6_tRTW = 0,
.t7_tWTR = 0,
.t8_tREFI= 0,
.mem_burst_latency = 0, // 0 - 1600l, 1 = 1866bl, 2 = 2133bl
.marikoCpuVmin = 600,
.eristaGpuVmin = 810,
.marikoGpuVmin = 610,
.marikoGpuVmax = 800,
// NOTE: These tables should NOT BE USED and are only here as placeholders. Always try and find your own optimal tables.
// Ensure the voltages actually increase or stay the sameot
.marikoGpuVoltArray = {
610 /* 76 */,
610 /* 153 */,
610 /* 230 */,
610 /* 307 */,
610 /* 384 */,
610 /* 460 */,
610 /* 537 */,
610 /* 614 */,
610 /* 691 */,
610 /* 768 */,
620 /* 844 */,
640 /* 921 */,
675 /* 998 */,
710 /* 1075 */,
735 /* 1152 */,
785 /* 1228 */,
0 /* 1267 (Disabled by default) */,
0 /* 1305 (Disabled by default) */,
0 /* 1344 (Disabled by default) */,
0 /* 1382 (Disabled by default) */,
0 /* 1420 (Disabled by default) */,
0 /* 1459 (Disabled by default) */,
0 /* 1497 (Disabled by default) */,
0 /* 1536 (Disabled by default) */,
},
.eristaGpuVoltArray = {
750 /* 76 */,
750 /* 115 */,
750 /* 153 */,
750 /* 192 */,
750 /* 230 */,
775 /* 269 */,
775 /* 307 */,
800 /* 346 */,
800 /* 384 */,
825 /* 422 */,
825 /* 460 */,
850 /* 499 */,
850 /* 537 */,
875 /* 576 */,
875 /* 614 */,
900 /* 652 */,
900 /* 691 */,
925 /* 729 */,
925 /* 768 */,
950 /* 806 */,
950 /* 844 */,
975 /* 883 */,
975 /* 921 */,
0 /* 960 (Disabled by default) */,
0 /* 998 (Disabled by default) */,
0 /* 1036 (Disabled by default) */,
0 /* 1075 (Disabled by default) */,
};
/* Advanced Settings:
* - Erista CPU DVFS Table:
*/
.eristaCpuDvfsTable = {
{ 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 } },
{ 1887000, { CPU_MAX_MAX_VOLT }, { 5100873, -279186, 4747 } },
{ 1963500, { CPU_MAX_MAX_VOLT }, { 5100873, -279186, 4747 } },
{ 2091000, { CPU_MAX_MAX_VOLT }, { 5100873, -279186, 4747 } },
{ 2193000, { CPU_MAX_MAX_VOLT }, { 5100873, -279186, 4747 } },
{ 2295000, { CPU_MAX_MAX_VOLT }, { 5100873, -279186, 4747 } },
},
/* - Mariko CPU DVFS Table:
* 2397000 might not work for some SoCs.
*/
.marikoCpuDvfsTable = {
{ 204000, { 721589, -12695, 27 }, {} },
{ 306000, { 747134, -14195, 27 }, {} },
{ 408000, { 776324, -15705, 27 }, {} },
{ 510000, { 809160, -17205, 27 }, {} },
{ 612000, { 845641, -18715, 27 }, {} },
{ 714000, { 885768, -20215, 27 }, {} },
{ 816000, { 929540, -21725, 27 }, {} },
{ 918000, { 976958, -23225, 27 }, {} },
{ 1020000, { 1028021, -24725, 27 }, { 1120000 } },
{ 1122000, { 1082730, -26235, 27 }, { 1120000 } },
{ 1224000, { 1141084, -27735, 27 }, { 1120000 } },
{ 1326000, { 1203084, -29245, 27 }, { 1120000 } },
{ 1428000, { 1268729, -30745, 27 }, { 1120000 } },
{ 1581000, { 1374032, -33005, 27 }, { 1120000 } },
{ 1683000, { 1448791, -34505, 27 }, { 1120000 } },
{ 1785000, { 1527196, -36015, 27 }, { 1120000 } },
{ 1887000, { 1609246, -37515, 27 }, { 1120000 } },
{ 1963500, { 1675751, -38635, 27 }, { 1120000 } },
{ 2091000, { 1716501, -39395, 27 }, { CPU_MAX_MAX_VOLT } },
{ 2193000, { 1775132, -40505, 27 }, { CPU_MAX_MAX_VOLT } },
{ 2295000, { 1866287, -42005, 27 }, { CPU_MAX_MAX_VOLT } },
},
.marikoCpuDvfsTableSLT = {
// { 204000, { 732856, -17335, 113 }, { } },
// { 306000, { 760024, -18195, 113 }, { } },
{ 408000, { 789258, -19055, 113 }, { } },
{ 510000, { 789258, -19915, 113 }, { } },
{ 612000, { 789258, -19055, 113 }, { } },
{ 714000, { 820558, -19915, 113 }, { } },
{ 816000, { 853926, -20775, 113 }, { } },
{ 918000, { 889361, -21625, 113 }, { } },
{ 1020000, { 926862, -22485, 113 }, { 1120000 } },
{ 1122000, { 926862, -22485, 113 }, { 1120000 } },
{ 1224000, { 926862, -22485, 113 }, { 1120000 } },
{ 1326000, { 966431, -23345, 113 }, { 1120000 } },
{ 1428000, { 1008066, -24205, 113 }, { 1120000 } },
{ 1581000, { 1051768, -25065, 113 }, { 1120000 } },
{ 1683000, { 1097537, -25925, 113 }, { 1120000 } },
{ 1785000, { 1145373, -26785, 113 }, { 1120000 } },
{ 1887000, { 1195276, -27645, 113 }, { 1120000 } },
{ 1963500, { 1274006, -29795, 113 }, { 1120000 } },
{ 2091000, { 1349076, -33235, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2193000, { 1386213, -33235, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2295000, { 1445416, -34095, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2397000, { 1490873, -34955, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2499000, { 1580725, -35815, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2601000, { 1702903, -36675, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2703000, { 1748360, -37535, 113 }, { CPU_MAX_MAX_VOLT } },
{ 2805000, { 1793817, -38395, 113 }, { CPU_MAX_MAX_VOLT } },
// { 2907000, { 1839274, -39255, 113 }, { CPU_MAX_MAX_VOLT } },
// { 3009000, { 1884731, -40115, 113 }, { CPU_MAX_MAX_VOLT } },
},
/* - Erista GPU DVFS Table:
*/
.eristaGpuDvfsTable = {
{ 76800, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 115200, { }, { 856185, 8144, -940, 808, -21583, 226 } },
{ 153600, { }, { 856185, 8144, -940, 808, -21583, 226 } },
{ 192000, { }, { 898077, 8144, -940, 808, -21583, 226 } },
{ 230400, { }, { 898077, 8144, -940, 808, -21583, 226 } },
{ 268800, { }, { 939968, 8144, -940, 808, -21583, 226 } },
{ 307200, { }, { 939968, 8144, -940, 808, -21583, 226 } },
{ 345600, { }, { 981860, 8144, -940, 808, -21583, 226 } },
{ 384000, { }, { 981860, 8144, -940, 808, -21583, 226 } },
{ 422400, { }, { 1023751, 8144, -940, 808, -21583, 226 } },
{ 460800, { }, { 1023751, 8144, -940, 808, -21583, 226 } },
{ 499200, { }, { 1065642, 8144, -940, 808, -21583, 226 } },
{ 537600, { }, { 1065642, 8144, -940, 808, -21583, 226 } },
{ 576000, { }, { 1107534, 8144, -940, 808, -21583, 226 } },
{ 614400, { }, { 1107534, 8144, -940, 808, -21583, 226 } },
{ 652800, { }, { 1149425, 8144, -940, 808, -21583, 226 } },
{ 691200, { }, { 1149425, 8144, -940, 808, -21583, 226 } },
{ 729600, { }, { 1191317, 8144, -940, 808, -21583, 226 } },
{ 768000, { }, { 1191317, 8144, -940, 808, -21583, 226 } },
{ 806400, { }, { 1233208, 8144, -940, 808, -21583, 226 } },
{ 844800, { }, { 1233208, 8144, -940, 808, -21583, 226 } },
{ 883200, { }, { 1275100, 8144, -940, 808, -21583, 226 } },
{ 921600, { }, { 1275100, 8144, -940, 808, -21583, 226 } },
// { 998400, { }, { 1316991, 8144, -940, 808, -21583, 226 } },
// { 1075200, { }, { 1358882, 8144, -940, 808, -21583, 226 } },
},
.eristaGpuDvfsTableSLT = {
{ 76800, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 115200, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 153600, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 192000, { }, { 856185, 8144, -940, 808, -21583, 226 } },
{ 230400, { }, { 856185, 8144, -940, 808, -21583, 226 } },
{ 268800, { }, { 898077, 8144, -940, 808, -21583, 226 } },
{ 307200, { }, { 898077, 8144, -940, 808, -21583, 226 } },
{ 345600, { }, { 939968, 8144, -940, 808, -21583, 226 } },
{ 384000, { }, { 939968, 8144, -940, 808, -21583, 226 } },
{ 422400, { }, { 981860, 8144, -940, 808, -21583, 226 } },
{ 460800, { }, { 981860, 8144, -940, 808, -21583, 226 } },
{ 499200, { }, { 1023751, 8144, -940, 808, -21583, 226 } },
{ 537600, { }, { 1023751, 8144, -940, 808, -21583, 226 } },
{ 576000, { }, { 1065642, 8144, -940, 808, -21583, 226 } },
{ 614400, { }, { 1065642, 8144, -940, 808, -21583, 226 } },
{ 652800, { }, { 1107534, 8144, -940, 808, -21583, 226 } },
{ 691200, { }, { 1107534, 8144, -940, 808, -21583, 226 } },
{ 729600, { }, { 1149425, 8144, -940, 808, -21583, 226 } },
{ 768000, { }, { 1149425, 8144, -940, 808, -21583, 226 } },
{ 806400, { }, { 1191317, 8144, -940, 808, -21583, 226 } },
{ 844800, { }, { 1191317, 8144, -940, 808, -21583, 226 } },
{ 883200, { }, { 1233208, 8144, -940, 808, -21583, 226 } },
{ 921600, { }, { 1233208, 8144, -940, 808, -21583, 226 } },
{ 960000, { }, { 1275100, 8144, -940, 808, -21583, 226 } },
{ 998400, { }, { 1275100, 8144, -940, 808, -21583, 226 } },
// { 1075200, { }, { 1316991, 8144, -940, 808, -21583, 226 } },
},
.eristaGpuDvfsTableHigh = {
{ 76800, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 115200, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 153600, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 192000, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 230400, { }, { 814294, 8144, -940, 808, -21583, 226 } },
{ 268800, { }, { 856185, 8144, -940, 808, -21583, 226 } },
{ 307200, { }, { 856185, 8144, -940, 808, -21583, 226 } },
{ 345600, { }, { 898077, 8144, -940, 808, -21583, 226 } },
{ 384000, { }, { 898077, 8144, -940, 808, -21583, 226 } },
{ 422400, { }, { 939968, 8144, -940, 808, -21583, 226 } },
{ 460800, { }, { 939968, 8144, -940, 808, -21583, 226 } },
{ 499200, { }, { 981860, 8144, -940, 808, -21583, 226 } },
{ 537600, { }, { 981860, 8144, -940, 808, -21583, 226 } },
{ 576000, { }, { 1023751, 8144, -940, 808, -21583, 226 } },
{ 614400, { }, { 1023751, 8144, -940, 808, -21583, 226 } },
{ 652800, { }, { 1065642, 8144, -940, 808, -21583, 226 } },
{ 691200, { }, { 1065642, 8144, -940, 808, -21583, 226 } },
{ 729600, { }, { 1107534, 8144, -940, 808, -21583, 226 } },
{ 768000, { }, { 1107534, 8144, -940, 808, -21583, 226 } },
{ 806400, { }, { 1149425, 8144, -940, 808, -21583, 226 } },
{ 844800, { }, { 1149425, 8144, -940, 808, -21583, 226 } },
{ 883200, { }, { 1191317, 8144, -940, 808, -21583, 226 } },
{ 921600, { }, { 1191317, 8144, -940, 808, -21583, 226 } },
{ 960000, { }, { 1233208, 8144, -940, 808, -21583, 226 } },
{ 998400, { }, { 1233208, 8144, -940, 808, -21583, 226 } },
{ 1036800, { }, { 1275100, 8144, -940, 808, -21583, 226 } },
{ 1075200, { }, { 1275100, 8144, -940, 808, -21583, 226 } },
},
/* - Mariko GPU DVFS Table:
* 1305600 might not work for some SoCs.
*/
.marikoGpuDvfsTable = {
{ 76800, {}, { 610000, } },
{ 153600, {}, { 610000, } },
{ 230400, {}, { 610000, } },
{ 307200, {}, { 610000, } },
{ 384000, {}, { 610000, } },
{ 460800, {}, { 610000, } },
{ 537600, {}, { 801688, -10900, -163, 298, -10599, 162 } },
{ 614400, {}, { 824214, -5743, -452, 238, -6325, 81 } },
{ 691200, {}, { 848830, -3903, -552, 119, -4030, -2 } },
{ 768000, {}, { 891575, -4409, -584, 0, -2849, 39 } },
{ 844800, {}, { 940071, -5367, -602, -60, -63, -93 } },
{ 921600, {}, { 986765, -6637, -614, -179, 1905, -13 } },
{ 998400, {}, { 1098475, -13529, -497, -179, 3626, 9 } },
{ 1075200, {}, { 1163644, -12688, -648, 0, 1077, 40 } },
{ 1152000, {}, { 1204812, -9908, -830, 0, 1469, 110 } },
{ 1228800, {}, { 1277303, -11675, -859, 0, 3722, 313 } },
{ 1267200, {}, { 1335531, -12567, -867, 0, 3681, 559 } },
// Appending table
//{ 1305600, {}, { 1374130, -13725, -859, 0, 4442, 576 } },
},
.marikoGpuDvfsTableSLT = {
{ 76800, {}, { 600000, } },
{ 153600, {}, { 600000, } },
{ 230400, {}, { 600000, } },
{ 307200, {}, { 600000, } },
{ 384000, {}, { 600000, } },
{ 460800, {}, { 795089, -11096, -163, 298, -10421, 162 } },
{ 537600, {}, { 795089, -11096, -163, 298, -10421, 162 } },
{ 614400, {}, { 820606, -6285, -452, 238, -6182, 81 } },
{ 691200, {}, { 846289, -4565, -552, 119, -3958, -2 } },
{ 768000, {}, { 888720, -5110, -584, 0, -2849, 39 } },
{ 844800, {}, { 936634, -6089, -602, -60, -99, -93 } },
{ 921600, {}, { 982562, -7373, -614, -179, 1797, -13 } },
{ 998400, {}, { 1090179, -14125, -497, -179, 3518, 9 } },
{ 1075200, {}, { 1155798, -13465, -648, 0, 1077, 40 } },
{ 1152000, {}, { 1198568, -10904, -830, 0, 1469, 110 } },
{ 1228800, {}, { 1269988, -12707, -859, 0, 3722, 313 } },
{ 1267200, {}, { 1308155, -13694, -867, 0, 3681, 559 } },
},
.marikoGpuDvfsTableHiOPT = {
{ 76800, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 153600, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 230400, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 307200, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 384000, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 460800, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 537600, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 614400, { }, { 590000, 0, 0, 0, 0, 0 }, },
{ 691200, { }, { 838712, -7304, -552, 119, -3750, -2 }, },
{ 768000, { }, { 880210, -7955, -584, 0, -2849, 39 }, },
{ 844800, { }, { 926398, -8892, -602, -60, -384, -93 }, },
{ 921600, { }, { 970060, -10108, -614, -179, 1508, -13 }, },
{ 998400, { }, { 1060665, -16075, -497, -179, 3213, 9 }, },
{ 1075200, { }, { 1117576, -16093, -648, 0, 1077, 40 }, },
{ 1152000, { }, { 1094475, -12688, -648, 0, 1077, 40 }, },
{ 1228800, { }, { 1124475, -12688, -648, 0, 1077, 40 }, },
{ 1267200, { }, { 1145060, -12688, -648, 0, 1077, 40 }, },
{ 1305600, { }, { 1163644, -12688, -648, 0, 1077, 40 }, },
{ 1344000, { }, { 1182228, -12688, -648, 0, 1077, 40 }, },
{ 1382400, { }, { 1200812, -12688, -648, 0, 1077, 40 }, },
{ 1420800, { }, { 1219396, -12688, -648, 0, 1077, 40 }, },
{ 1459200, { }, { 1237980, -12688, -648, 0, 1077, 40 }, },
{ 1497600, { }, { 1256564, -12688, -648, 0, 1077, 40 }, },
{ 1536000, { }, { 1275148, -12688, -648, 0, 1077, 40 }, },
},
//.eristaMtcTable = const_cast<EristaMtcTable *>(&EristaMtcTablePlaceholder),
//.marikoMtcTable = const_cast<MarikoMtcTable *>(&MarikoMtcTablePlaceholder),
};
}

View File

@@ -1,109 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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
#define CUST_REV 11
#include "oc_common.hpp"
#include "pcv/pcv_common.hpp"
namespace ams::ldr::oc {
#include "mtc_timing_table.hpp"
enum MtcConfig: u32 {
AUTO_ADJ = 0,
};
using CustomizeCpuDvfsTable = pcv::cvb_entry_t[pcv::DvfsTableEntryLimit];
using CustomizeGpuDvfsTable = pcv::cvb_entry_t[pcv::DvfsTableEntryLimit];
static_assert(sizeof(CustomizeCpuDvfsTable) == sizeof(CustomizeGpuDvfsTable));
static_assert(sizeof(CustomizeCpuDvfsTable) == sizeof(pcv::cvb_entry_t) * pcv::DvfsTableEntryLimit);
constexpr uint32_t ERISTA_MTC_MAGIC = 0x43544D45; // EMTC
constexpr uint32_t MARIKO_MTC_MAGIC = 0x43544D4D; // MMTC
typedef struct CustomizeTable {
u8 cust[4] = {'C', 'U', 'S', 'T'};
u32 custRev = CUST_REV;
u32 mtcConf = AUTO_ADJ;
u32 commonCpuBoostClock;
u32 commonEmcMemVolt;
u32 eristaCpuMaxVolt;
u32 eristaEmcMaxClock;
u32 marikoCpuMaxVolt;
u32 marikoEmcMaxClock;
u32 marikoEmcVddqVolt;
u32 marikoCpuUV;
u32 marikoGpuUV;
u32 eristaCpuUV;
u32 eristaGpuUV;
u32 commonGpuVoltOffset;
u32 EmcDvbShift;
// advanced config
u32 t1_tRCD;
u32 t2_tRP;
u32 t3_tRAS;
u32 t4_tRRD;
u32 t5_tRFC;
u32 t6_tRTW;
u32 t7_tWTR;
u32 t8_tREFI;
u32 mem_burst_latency;
u32 marikoCpuVmin;
u32 eristaGpuVmin;
u32 marikoGpuVmin;
u32 marikoGpuVmax;
u32 marikoGpuVoltArray[24];
u32 eristaGpuVoltArray[27];
CustomizeCpuDvfsTable eristaCpuDvfsTable;
CustomizeCpuDvfsTable marikoCpuDvfsTable;
CustomizeCpuDvfsTable marikoCpuDvfsTableSLT;
CustomizeGpuDvfsTable eristaGpuDvfsTable;
CustomizeGpuDvfsTable eristaGpuDvfsTableSLT;
CustomizeGpuDvfsTable eristaGpuDvfsTableHigh;
CustomizeGpuDvfsTable marikoGpuDvfsTable;
CustomizeGpuDvfsTable marikoGpuDvfsTableSLT;
CustomizeGpuDvfsTable marikoGpuDvfsTableHiOPT;
//EristaMtcTable* eristaMtcTable;
//MarikoMtcTable* marikoMtcTable;
} CustomizeTable;
//static_assert(sizeof(CustomizeTable) == sizeof(u8) * 4 + sizeof(u32) * 10 + sizeof(CustomizeCpuDvfsTable) * 5 + sizeof(void*) * 2);
//static_assert(sizeof(CustomizeTable) == 7000);
extern volatile CustomizeTable C;
//extern volatile EristaMtcTable EristaMtcTablePlaceholder;
//extern volatile MarikoMtcTable MarikoMtcTablePlaceholder;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,151 +0,0 @@
/*
* Copyright (c) 2023 hanai3Bi
*
* 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"
namespace ams::ldr::oc {
#define MAX(A, B) std::max(A, B)
#define MIN(A, B) std::min(A, B)
#define CEIL(A) std::ceil(A)
#define FLOOR(A) std::floor(A)
/* Primary timings. */
const std::array<double, 8> tRCD_values = {18, 17, 16, 15, 14, 13, 12, 11};
const std::array<double, 8> tRP_values = {18, 17, 16, 15, 14, 13, 12, 11};
const std::array<double, 10> tRAS_values = {42, 36, 34, 32, 30, 28, 26, 24, 22, 20};
/* Secondary timings. */
const std::array<double, 8> tRRD_values = {10.0, 7.5, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0};
const std::array<double, 6> tRFC_values = {140, 120, 100, 80, 60, 40};
const std::array<u32, 10> tRTW_values = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; /* Is this even correct? */
const std::array<double, 10> tWTR_values = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
const std::array<u32, 7> tREFpb_values = {488, 732, 488 * 2, 488 * 3, 488 * 4, 488 * 6, 488 * 8}; /* TODO: Figure out if it's actually 8 and if this is even right. */
const u32 BL = 16;
const u32 RL = 28 + C.mem_burst_latency;
const u32 WL = 14 + C.mem_burst_latency;
/* Refresh Cycle time. (All Banks) */
const u32 tRFCab = (u32)(tRFC_values[C.t5_tRFC] * 1.5);
/* Precharge to Precharge Delay. (Cycles) */
/* Don't touch! */
const u32 tPPD = 4;
/* Four-bank ACTIVATE Window */
const u32 tFAW = 30;
/* DQS output access time from CK_t/CK_c. */
const double tDQSCK_max = 3.5;
const double tWPRE = 2.0;
/* tCK Read postamble. */
const double tRPST = 0.5;
namespace pcv::erista {
/* tCK_avg may have to be improved... */
const double tCK_avg = 1000'000.0 / C.eristaEmcMaxClock;
/* Primary timings. */
const double tRCD = MAX(tRCD_values[C.t1_tRCD], 4.0 * tCK_avg);
const double tRPpb = MAX(tRP_values[C.t2_tRP], 4.0 * tCK_avg);
const double tRAS = MAX(tRAS_values[C.t3_tRAS], 3.0 * tCK_avg);
/* Secondary timings. */
const double tRRD = MAX(tRRD_values[C.t4_tRRD], 4.0 * tCK_avg);
const double tRFCpb = tRFC_values[C.t5_tRFC];
const u32 tRTW = tRTW_values[C.t6_tRTW];
const double tWTR = MAX(tWTR_values[C.t7_tWTR], 8.0 * tCK_avg);
const u32 tREFpb = tREFpb_values[C.t8_tREFI];
/* Latency stuff. */
const u32 R2W = CEIL(RL + CEIL(tDQSCK_max/tCK_avg) + (BL/2) - WL + tWPRE + FLOOR(tRPST)) + 6;
const u32 W2R = WL + (BL/2) + 1 + tWTR - 4;
const u32 WTP = WL + (BL/2) + 1 + tWTR - 6;
/* Refresh stuff. */
const u32 numOfRows = 65536;
const u32 REFRESH = MIN((u32)65472, u32(std::ceil((double(tREFpb) * C.eristaEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4);
const u32 REFBW = MIN((u32)65536, REFRESH+64);
/* Do not touch stuff. */
/* ACTIVATE-to-ACTIVATE command period. (same bank) */
const double tRC = tRAS + tRPpb;
/* Minimum Self-Refresh Time. (Entry to Exit) */
const double tSR = MAX(15.0, 3.0 * tCK_avg);
/* SELF REFRESH exit to next valid command delay. */
const double tXSR = MAX(tRFCab + 7.5, 2.0 * tCK_avg);
/* Exit power down to next valid command delay. */
const double tXP = MAX(7.5, 5.0 * tCK_avg);
/* Internal READ to PRECHARGE command delay. */
const double tRTP = MAX(7.5, 8.0 * tCK_avg);
/* Row Precharge Time. (all banks) */
const double tRPab = MAX(21.0, 4.0 * tCK_avg);
}
namespace pcv::mariko {
/* tCK_avg may have to be improved... */
const double tCK_avg = 1000'000.0 / C.marikoEmcMaxClock;
/* Primary timings. */
const double tRCD = MAX(tRCD_values[C.t1_tRCD], 4.0 * tCK_avg);
const double tRPpb = MAX(tRP_values[C.t2_tRP], 4.0 * tCK_avg);
const double tRAS = MAX(tRAS_values[C.t3_tRAS], 3.0 * tCK_avg);
/* Secondary timings. */
const double tRRD = MAX(tRRD_values[C.t4_tRRD], 4.0 * tCK_avg);
const double tRFCpb = tRFC_values[C.t5_tRFC];
const u32 tRTW = tRTW_values[C.t6_tRTW];
const double tWTR = MAX(tWTR_values[C.t7_tWTR], 8.0 * tCK_avg);
const u32 tREFpb = tREFpb_values[C.t8_tREFI];
/* Latency stuff. */
const u32 R2W = CEIL(RL + CEIL(tDQSCK_max/tCK_avg) + (BL/2) - WL + tWPRE + FLOOR(tRPST)) + 6;
const u32 W2R = WL + (BL/2) + 1 + tWTR - 6;
const u32 WTP = WL + (BL/2) + 1 + tWTR - 8;
/* Refresh stuff. */
const u32 numOfRows = 65536;
const u32 REFRESH = MIN((u32)65472, u32(std::ceil((double(tREFpb) * C.eristaEmcMaxClock / numOfRows * 1.048 / 2 - 64))) / 4 * 4);
const u32 REFBW = MIN((u32)65536, REFRESH+64);
/* Do not touch stuff. */
/* ACTIVATE-to-ACTIVATE command period. (same bank) */
const double tRC = tRAS + tRPpb;
/* Minimum Self-Refresh Time. (Entry to Exit) */
const double tSR = MAX(15.0, 3.0 * tCK_avg);
/* SELF REFRESH exit to next valid command delay. */
const double tXSR = MAX(tRFCab + 7.5, 2.0 * tCK_avg);
/* Exit power down to next valid command delay. */
const double tXP = MAX(7.5, 5.0 * tCK_avg);
/* Internal READ to PRECHARGE command delay. */
const double tRTP = MAX(7.5, 8.0 * tCK_avg);
/* Row Precharge Time. (all banks) */
const double tRPab = MAX(21.0, 4.0 * tCK_avg);
}
}

View File

@@ -1,102 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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
#ifdef ATMOSPHERE_IS_STRATOSPHERE
#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(); }
#else
#include "oc_test.hpp"
#endif
#include "customize.hpp"
#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);
}
namespace ams::ldr::oc {
template<typename Pointer>
struct PatcherEntry {
using patternFn = bool(*)(Pointer* ptr);
using patcherFn = Result(*)(Pointer* ptr);
const char* description;
patcherFn patcher_fn = nullptr;
size_t maximum_patched_count = 0;
patternFn pattern_search_fn = nullptr;
Pointer value_search;
size_t patched_count = 0;
Result Apply(Pointer* ptr) {
Result res = patcher_fn(ptr);
if (R_SUCCEEDED(res))
patched_count++;
return res;
}
Result SearchAndApply(Pointer* ptr) {
bool searchOk = false;
if (pattern_search_fn) {
if (pattern_search_fn(ptr)) searchOk = true;
} else {
if (value_search == *(ptr)) searchOk = true;
}
if (searchOk)
return Apply(ptr);
R_THROW(ldr::ResultUnsuccessfulPatcher());
}
Result CheckResult() {
#ifndef ATMOSPHERE_IS_STRATOSPHERE
R_UNLESS(patched_count > 0, ldr::ResultUnsuccessfulPatcher());
#endif
if (maximum_patched_count)
R_UNLESS(patched_count <= maximum_patched_count, ldr::ResultUnsuccessfulPatcher());
R_SUCCEED();
}
};
}

View File

@@ -1,23 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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 "oc_common.hpp"
#include "pcv/pcv.hpp"
#include "ptm/ptm.hpp"

View File

@@ -1,214 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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/>.
*/
#ifndef ATMOSPHERE_IS_STRATOSPHERE
#include "oc_test.hpp"
#include "oc_loader.hpp"
void* loadExec(const char* file_loc, size_t* out_size) {
FILE* fp = fopen(file_loc, "rb");
if (!fp) {
fprintf(stderr, "Cannot open file: \"%s\"\n", file_loc);
exit(-1);
}
if (fseek(fp, 0, SEEK_END) < 0) {
fprintf(stderr, "fseek error\n");
exit(-1);
}
long size = ftell(fp);
if (size == -1L) {
fprintf(stderr, "\"%s\" is a directory", file_loc);
exit(-1);
}
fseek(fp, 0, SEEK_SET);
void* buf = malloc(size);
fread(buf, size, 1, fp);
fclose(fp);
if (size < 8192) {
fprintf(stderr, "File is too small to process: \"%s\" (%ld B)\n", file_loc, size);
exit(-1);
}
*out_size = size;
return buf;
}
void saveExec(const char* file_loc, const void* buf, size_t size) {
FILE* fp = fopen(file_loc, "wb");
if (!fp) {
fprintf(stderr, "Cannot write to \"%s\"\n", file_loc);
exit(-1);
}
printf("Saving to \"%s\"...\n", file_loc);
fwrite(buf, size, 1, fp);
fclose(fp);
}
Result Test_PcvDvfsTable() {
using namespace ams::ldr::oc::pcv;
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&mariko::CpuCvbTableDefault)) == 18);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&erista::CpuCvbTableDefault)) == 16);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&mariko::GpuCvbTableDefault)) == 17);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&erista::GpuCvbTableDefault)) == 12);
cvb_entry_t last_mariko_cpu_cvb_entry_default = { 1963500, { 1675751, -38635, 27 }, { 1120000 } };
assert(memcmp(GetDvfsTableLastEntry((cvb_entry_t *)(&mariko::CpuCvbTableDefault)), (void *)&last_mariko_cpu_cvb_entry_default, sizeof(last_mariko_cpu_cvb_entry_default)) == 0);
assert(GetDvfsTableLastEntry((cvb_entry_t *)(&erista::GpuCvbTableDefault))->freq == 921600);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTableSLT)) == 25);
// Customized table default
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.eristaCpuDvfsTable)) == 19);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTable)) == 21);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoCpuDvfsTableSLT)) == 22);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.eristaGpuDvfsTable)) == 12);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTable)) == 17);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTableSLT)) == 17);
assert(GetDvfsTableEntryCount((cvb_entry_t *)(&ams::ldr::oc::C.marikoGpuDvfsTableHiOPT)) == 17);
constexpr size_t limit = ams::ldr::oc::pcv::DvfsTableEntryLimit;
cvb_entry_t customized_table[limit] = {};
for (size_t i = 0; i < limit; i++) {
assert(GetDvfsTableEntryCount(customized_table) == i);
auto p = GetDvfsTableLastEntry(customized_table);
if (p)
assert(p->freq == i);
customized_table[i].freq = i + 1;
}
R_SUCCEED();
}
void unitTest() {
UnitTest test[] = {
{ "PCV DVFS Table", &Test_PcvDvfsTable }
};
for (auto &t : test) {
t.Test();
}
}
int main(int argc, char** argv) {
unitTest();
const char* pcv_opt = "pcv";
const char* ptm_opt = "ptm";
const char* save_opt = "-s";
const char* mariko_ext = ".mariko";
const char* erista_ext = ".erista";
enum EXE_OPTION {
EXE_PCV,
EXE_PTM,
UNKNOWN
};
EXE_OPTION exe_opt = UNKNOWN;
if (argc > 2) {
if (!strcmp(argv[1], pcv_opt))
exe_opt = EXE_PCV;
if (!strcmp(argv[1], ptm_opt))
exe_opt = EXE_PTM;
}
if ((argc != 3 && argc != 4) || exe_opt == UNKNOWN) {
fprintf(stderr, "Usage:\n"\
" %s %s | %s [%s] <exec_path>\n\n"\
" %s : Save patched executable with extension \"%s\" / \"%s\"\n"
, argv[0], pcv_opt, ptm_opt, save_opt
, save_opt, mariko_ext, erista_ext);
return -1;
}
bool save_patched = false;
char* exec_path = argv[2];
if (argc == 4 && !strcmp(argv[2], save_opt)) {
save_patched = true;
exec_path = argv[3];
}
size_t file_size;
void* file_buffer = loadExec(exec_path, &file_size);
size_t exec_path_len = strlen(reinterpret_cast<const char *>(exec_path));
size_t exec_path_patched_len = exec_path_len + std::max(strlen(mariko_ext), strlen(erista_ext)) + 1;
if (exe_opt == EXE_PCV) {
ams::ldr::oc::pcv::SafetyCheck();
{
void* erista_buf = malloc(file_size);
std::memcpy(erista_buf, file_buffer, file_size);
printf("Patching %s for Erista...\n", pcv_opt);
ams::ldr::oc::pcv::erista::Patch(reinterpret_cast<uintptr_t>(erista_buf), file_size);
if (save_patched) {
char* exec_path_erista = reinterpret_cast<char *>(malloc(exec_path_patched_len));
strncpy(exec_path_erista, exec_path, exec_path_patched_len);
strncat(exec_path_erista, erista_ext, exec_path_patched_len);
saveExec(exec_path_erista, erista_buf, file_size);
free(exec_path_erista);
}
free(erista_buf);
}
{
void* mariko_buf = malloc(file_size);
std::memcpy(mariko_buf, file_buffer, file_size);
printf("Patching %s for Mariko...\n", pcv_opt);
ams::ldr::oc::pcv::mariko::Patch(reinterpret_cast<uintptr_t>(mariko_buf), file_size);
if (save_patched) {
char* exec_path_mariko = reinterpret_cast<char *>(malloc(exec_path_patched_len));
strncpy(exec_path_mariko, exec_path, exec_path_patched_len);
strncat(exec_path_mariko, mariko_ext, exec_path_patched_len);
saveExec(exec_path_mariko, mariko_buf, file_size);
free(exec_path_mariko);
}
free(mariko_buf);
}
}
if (exe_opt == EXE_PTM) {
void* mariko_buf = malloc(file_size);
std::memcpy(mariko_buf, file_buffer, file_size);
printf("Patching %s (Mariko Only)...\n", ptm_opt);
ams::ldr::oc::ptm::Patch(reinterpret_cast<uintptr_t>(mariko_buf), file_size);
if (save_patched) {
char* exec_path_mariko = reinterpret_cast<char *>(malloc(exec_path_patched_len));
strncpy(exec_path_mariko, exec_path, exec_path_patched_len);
strncat(exec_path_mariko, mariko_ext, exec_path_patched_len);
saveExec(exec_path_mariko, mariko_buf, file_size);
free(exec_path_mariko);
}
free(mariko_buf);
}
free(file_buffer);
printf("Passed!\n\n");
return 0;
}
#endif

View File

@@ -1,81 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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
#ifndef ATMOSPHERE_IS_STRATOSPHERE
#include <cassert>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int Result;
#define R_SUCCEEDED(arg) (arg == 0)
#define R_FAILED(arg) (arg != 0)
#define LOGGING(fmt, ...) { printf(fmt "\n", ##__VA_ARGS__); }
#define CRASH(msg, ...) { fprintf(stderr, "%s\nFailed in %s!\n", msg, __PRETTY_FUNCTION__); exit(-1); }
#define R_SUCCEED() { return 0; }
#define R_THROW(err) { return err; }
#define R_TRY(expr) { Result _rc = (expr); if (R_FAILED(_rc)) { return _rc; } }
#define R_UNLESS(expr, rc) { if (!(expr)) { return rc; } }
#define R_DEFINE_ERROR_RESULT(name, rc) \
inline Result Result##name() { return rc; }
#define HEXDUMP(ptr, len) \
{ \
const uint8_t* p = reinterpret_cast<const uint8_t *>(ptr); \
size_t i, j; \
for (i = 0; i < len; i += 16) { \
printf("%06zx: ", i); \
for (j = 0; j < 16 && i + j < len; j++) \
printf("%02x ", p[i + j]); \
for (; j < 16; j++) \
printf(" "); \
for (j = 0; j < 16 && i + j < len; j++) \
printf("%c", isprint(p[i + j]) ? p[i + j] : '.'); \
printf("\n"); \
} \
} \
typedef struct UnitTest {
using Func = Result(*)();
const char* description;
Func fun = nullptr;
void Test() {
Result res = fun();
if (R_FAILED(res)) {
CRASH(description);
}
}
} UnitTest;
#endif

View File

@@ -1,174 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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::oc::pcv {
Result MemFreqPllmLimit(u32* ptr) {
clk_pll_param* entry = reinterpret_cast<clk_pll_param *>(ptr);
R_UNLESS(entry->freq == entry->vco_max, ldr::ResultInvalidMemPllmEntry());
// Double the max clk simply
u32 max_clk = entry->freq * 2;
entry->freq = max_clk;
entry->vco_max = max_clk;
R_SUCCEED();
}
Result MemVoltHandler(u32* ptr) {
// ptr value might be default_uv or max_uv
regulator* entries[2] = {
reinterpret_cast<regulator *>(reinterpret_cast<u8 *>(ptr) - offsetof(regulator, type_1.default_uv)),
reinterpret_cast<regulator *>(reinterpret_cast<u8 *>(ptr) - offsetof(regulator, type_1.max_uv)),
};
constexpr u32 uv_step = 12'500;
constexpr u32 uv_min = 600'000;
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());
R_UNLESS(entry->type_1.step_uv == uv_step, ldr::ResultInvalidRegulatorEntry());
R_UNLESS(entry->type_1.min_uv == uv_min, ldr::ResultInvalidRegulatorEntry());
R_SUCCEED();
};
regulator* entry = nullptr;
for (auto& i : entries) {
if (R_SUCCEEDED(validator(i)))
entry = i;
}
R_UNLESS(entry, ldr::ResultInvalidRegulatorEntry());
u32 emc_uv = C.commonEmcMemVolt;
if (!emc_uv)
R_SKIP();
if (emc_uv % uv_step)
emc_uv = emc_uv / uv_step * uv_step; // rounding
PATCH_OFFSET(ptr, emc_uv);
R_SUCCEED();
}
void SafetyCheck() {
if (C.custRev != CUST_REV)
CRASH("Triggered");
struct sValidator {
volatile u32 value;
u32 min;
u32 max;
bool value_required = false;
Result check() {
if (!value_required && !value)
R_SUCCEED();
if (min && value < min)
R_THROW(ldr::ResultSafetyCheckFailure());
if (max && value > max)
R_THROW(ldr::ResultSafetyCheckFailure());
R_SUCCEED();
}
};
u32 eristaCpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq);
u32 marikoCpuDvfsMaxFreq;
if (C.marikoCpuUV) {
marikoCpuDvfsMaxFreq = static_cast<u32>(
GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq
);
} else {
marikoCpuDvfsMaxFreq = static_cast<u32>(
GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq
);
}
u32 eristaGpuDvfsMaxFreq;
switch (C.eristaGpuUV)
{
case 0:
eristaGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq);
break;
case 1:
eristaGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaGpuDvfsTableSLT)->freq);
break;
case 2:
case 3:
eristaGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaGpuDvfsTableHigh)->freq);
break;
default:
eristaGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq);
break;
}
u32 marikoGpuDvfsMaxFreq;
switch (C.marikoGpuUV) {
case 0:
marikoGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq);
break;
case 1:
marikoGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoGpuDvfsTableSLT)->freq);
break;
case 2:
case 3:
marikoGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq);
break;
default:
marikoGpuDvfsMaxFreq = static_cast<u32>(GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq);
break;
}
sValidator validators[] = {
{ C.commonCpuBoostClock, 1020'000, 3000'000, true },
{ C.commonEmcMemVolt, 1100'000, 1500'000 }, // Official burst vmax for the RAMs
{ C.eristaCpuMaxVolt, 1100, 1300 },
{ C.eristaEmcMaxClock, 1600'000, 2600'200 },
{ C.marikoCpuMaxVolt, 1100, 1300 },
{ C.marikoEmcMaxClock, 1600'000, 3500'000 },
{ C.marikoEmcVddqVolt, 550'000, 700'000 },
{ eristaCpuDvfsMaxFreq, 1785'000, 3000'000 },
{ marikoCpuDvfsMaxFreq, 1785'000, 3000'000 },
{ eristaGpuDvfsMaxFreq, 768'000, 1228'000 },
{ marikoGpuDvfsMaxFreq, 768'000, 1536'000 },
};
for (auto& i : validators) {
if (R_FAILED(i.check()))
CRASH("Triggered");
}
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
#ifdef ATMOSPHERE_IS_STRATOSPHERE
SafetyCheck();
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
if (isMariko)
mariko::Patch(mapped_nso, nso_size);
else
erista::Patch(mapped_nso, nso_size);
#endif
}
}

View File

@@ -1,454 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 "../oc_common.hpp"
#include "pcv_common.hpp"
namespace ams::ldr::oc::pcv
{
namespace mariko
{
constexpr cvb_entry_t CpuCvbTableDefault[] = {
// CPUB01_CVB_TABLE
{204000, {721589, -12695, 27}, {}},
{306000, {747134, -14195, 27}, {}},
{408000, {776324, -15705, 27}, {}},
{510000, {809160, -17205, 27}, {}},
{612000, {845641, -18715, 27}, {}},
{714000, {885768, -20215, 27}, {}},
{816000, {929540, -21725, 27}, {}},
{918000, {976958, -23225, 27}, {}},
{1020000, {1028021, -24725, 27}, {1120000}},
{1122000, {1082730, -26235, 27}, {1120000}},
{1224000, {1141084, -27735, 27}, {1120000}},
{1326000, {1203084, -29245, 27}, {1120000}},
{1428000, {1268729, -30745, 27}, {1120000}},
{1581000, {1374032, -33005, 27}, {1120000}},
{1683000, {1448791, -34505, 27}, {1120000}},
{1785000, {1527196, -36015, 27}, {1120000}},
{1887000, {1609246, -37515, 27}, {1120000}},
{1963500, {1675751, -38635, 27}, {1120000}},
{},
};
constexpr int gpuVmax = 750;
constexpr int gpuVmin = 610;
constexpr u16 CpuMinVolts[] = {800, 637, 620, 610};
constexpr u32 CpuClkOfficial = 1963'500;
constexpr u32 CpuVoltOfficial = 1120;
constexpr cvb_entry_t GpuCvbTableDefault[] = {
// GPUB01_NA_CVB_TABLE
{76800, {}, {
610000,
}},
{153600, {}, {
610000,
}},
{230400, {}, {
610000,
}},
{307200, {}, {
610000,
}},
{384000, {}, {
610000,
}},
{460800, {}, {
610000,
}},
{537600, {}, {801688, -10900, -163, 298, -10599, 162}},
{614400, {}, {824214, -5743, -452, 238, -6325, 81}},
{691200, {}, {848830, -3903, -552, 119, -4030, -2}},
{768000, {}, {891575, -4409, -584, 0, -2849, 39}},
{844800, {}, {940071, -5367, -602, -60, -63, -93}},
{921600, {}, {986765, -6637, -614, -179, 1905, -13}},
{998400, {}, {1098475, -13529, -497, -179, 3626, 9}},
{1075200, {}, {1163644, -12688, -648, 0, 1077, 40}},
{1152000, {}, {1204812, -9908, -830, 0, 1469, 110}},
{1228800, {}, {1277303, -11675, -859, 0, 3722, 313}},
{1267200, {}, {1335531, -12567, -867, 0, 3681, 559}},
{},
};
constexpr u32 GpuClkPllLimit = 1300'000'000;
/* 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 asm_pattern[] = {0x52820000, 0x72A001C0};
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2)
{ return ((ins1 ^ ins2) >> 5) == 0; };
inline auto asm_get_rd = [](u32 ins)
{ return ins & ((1 << 5) - 1); };
inline auto asm_set_rd = [](u32 ins, u8 rd)
{ return (ins & 0xFFFFFFE0) | (rd & 0x1F); };
inline auto asm_set_imm16 = [](u32 ins, u16 imm)
{ return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5); };
inline bool GpuMaxClockPatternFn(u32 *ptr32)
{
return asm_compare_no_rd(*ptr32, asm_pattern[0]);
}
constexpr emc_dvb_dvfs_table_t EmcDvbTableDefault[] = {
{204000, {
637,
637,
637,
}},
{408000, {
637,
637,
637,
}},
{800000, {
637,
637,
637,
}},
{1065600, {
637,
637,
637,
}},
{1331200, {
650,
637,
637,
}},
{1600000, {
675,
650,
637,
}},
};
constexpr u32 EmcClkOSAlt = 1331'200;
constexpr u32 EmcClkPllmLimit = 2133'000'000;
constexpr u32 EmcVddqDefault = 600'000;
constexpr u32 MemVdd2Default = 1100'000;
constexpr u32 MTC_TABLE_REV = 3;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
namespace 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 int gpuVmin = 810;
constexpr u32 CpuVoltOfficial = 1235;
constexpr u32 CpuVoltL4T = 1235'000;
constexpr u16 CpuMinVolts[] = {950, 850, 825, 810};
inline bool CpuMaxVoltPatternFn(u32 *ptr32)
{
u32 val = *ptr32;
return (val == 1132 || val == 1170 || val == 1227);
}
constexpr u32 GpuClkPllLimit = 921'600'000;
/* 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 asm_pattern[] = {0x52820000, 0x72A001C0};
inline auto asm_compare_no_rd = [](u32 ins1, u32 ins2)
{ return ((ins1 ^ ins2) >> 5) == 0; };
inline auto asm_get_rd = [](u32 ins)
{ return ins & ((1 << 5) - 1); };
inline auto asm_set_rd = [](u32 ins, u8 rd)
{ return (ins & 0xFFFFFFE0) | (rd & 0x1F); };
inline auto asm_set_imm16 = [](u32 ins, u16 imm)
{ return (ins & 0xFFE0001F) | ((imm & 0xFFFF) << 5); };
inline bool GpuMaxClockPatternFn(u32 *ptr32)
{
return asm_compare_no_rd(*ptr32, asm_pattern[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 MemVoltHOS = 1125'000;
constexpr u32 EmcClkPllmLimit = 1866'000'000;
constexpr u32 MTC_TABLE_REV = 7;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}
template <bool isMariko>
Result CpuFreqCvbTable(u32 *ptr)
{
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::CpuCvbTableDefault) : (cvb_entry_t *)(&erista::CpuCvbTableDefault);
cvb_entry_t *customize_table = nullptr; // impossible to reach, there will always be a way to set a pointer
if (isMariko) {
if (C.marikoCpuUV) {
customize_table = const_cast<cvb_entry_t *>(C.marikoCpuDvfsTableSLT);
} else {
customize_table = const_cast<cvb_entry_t *>(C.marikoCpuDvfsTable);
}
} else {
customize_table = const_cast<cvb_entry_t *>(C.eristaCpuDvfsTable);
}
u32 cpu_max_volt = isMariko ? C.marikoCpuMaxVolt : C.eristaCpuMaxVolt;
u32 cpu_freq_threshold = 1020'000;
if (isMariko)
{
cpu_freq_threshold = C.marikoCpuUV ? 2193'000 : 2091'000;
}
else
{
cpu_freq_threshold = cpu_max_volt >= 1300 ? 1887'000 : 1428'000;
}
size_t default_entry_count = GetDvfsTableEntryCount(default_table);
size_t default_table_size = default_entry_count * sizeof(cvb_entry_t);
size_t customize_entry_count = GetDvfsTableEntryCount(customize_table);
size_t customize_table_size = customize_entry_count * sizeof(cvb_entry_t);
// Validate existing table
cvb_entry_t *table_free = reinterpret_cast<cvb_entry_t *>(ptr) + 1;
void *cpu_cvb_table_head = reinterpret_cast<u8 *>(table_free) - default_table_size;
bool validated = std::memcmp(cpu_cvb_table_head, default_table, default_table_size) == 0;
R_UNLESS(validated, ldr::ResultInvalidCpuDvfs());
std::memcpy(cpu_cvb_table_head, static_cast<void *>(customize_table), customize_table_size);
// Patch CPU max volt
if (cpu_max_volt)
{
cvb_entry_t *entry = static_cast<cvb_entry_t *>(cpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; i++)
{
if (entry->freq >= cpu_freq_threshold)
{
if (isMariko)
{
PATCH_OFFSET(&(entry->cvb_pll_param.c0), cpu_max_volt * 1000);
}
else
{
PATCH_OFFSET(&(entry->cvb_dfll_param.c0), cpu_max_volt * 1000);
}
}
entry++;
}
}
R_SUCCEED();
}
template <bool isMariko>
Result GpuFreqCvbTable(u32 *ptr)
{
cvb_entry_t *default_table = isMariko ? (cvb_entry_t *)(&mariko::GpuCvbTableDefault) : (cvb_entry_t *)(&erista::GpuCvbTableDefault);
cvb_entry_t *customize_table;
if (isMariko)
{
switch (C.marikoGpuUV)
{
case 0:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTable);
break;
case 1:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableSLT);
break;
case 2:
case 3:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTableHiOPT);
break;
default:
customize_table = const_cast<cvb_entry_t *>(C.marikoGpuDvfsTable);
break;
}
}
else
{
switch (C.eristaGpuUV)
{
case 0:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable);
break;
case 1:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTableSLT);
break;
case 2:
case 3:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTableHigh);
break;
default:
customize_table = const_cast<cvb_entry_t *>(C.eristaGpuDvfsTable);
break;
}
}
size_t default_entry_count = GetDvfsTableEntryCount(default_table);
size_t default_table_size = default_entry_count * sizeof(cvb_entry_t);
size_t customize_entry_count = GetDvfsTableEntryCount(customize_table);
size_t customize_table_size = customize_entry_count * sizeof(cvb_entry_t);
// Validate existing table
cvb_entry_t *table_free = reinterpret_cast<cvb_entry_t *>(ptr) + 1;
void *gpu_cvb_table_head = reinterpret_cast<u8 *>(table_free) - default_table_size;
bool validated = std::memcmp(gpu_cvb_table_head, default_table, default_table_size) == 0;
R_UNLESS(validated, ldr::ResultInvalidGpuDvfs());
std::memcpy(gpu_cvb_table_head, (void *)customize_table, customize_table_size);
// Patch GPU volt
if (C.marikoGpuUV == 3 || C.eristaGpuUV == 3)
{
cvb_entry_t *entry = static_cast<cvb_entry_t *>(gpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; i++)
{
if (isMariko)
{
if (C.marikoGpuVoltArray[i] == 0)
{
continue;
}
PATCH_OFFSET(&(entry->cvb_pll_param.c0), C.marikoGpuVoltArray[i] * 1000);
}
else
{
if (C.eristaGpuVoltArray[i] == 0)
{
continue;
}
PATCH_OFFSET(&(entry->cvb_pll_param.c0), C.eristaGpuVoltArray[i] * 1000);
}
PATCH_OFFSET(&(entry->cvb_pll_param.c1), 0);
PATCH_OFFSET(&(entry->cvb_pll_param.c2), 0);
PATCH_OFFSET(&(entry->cvb_pll_param.c3), 0);
PATCH_OFFSET(&(entry->cvb_pll_param.c4), 0);
PATCH_OFFSET(&(entry->cvb_pll_param.c5), 0);
entry++;
}
}
else if (C.commonGpuVoltOffset)
{
cvb_entry_t *entry = static_cast<cvb_entry_t *>(gpu_cvb_table_head);
for (size_t i = 0; i < customize_entry_count; i++)
{
PATCH_OFFSET(&(entry->cvb_pll_param.c0), (entry->cvb_pll_param.c0 - C.commonGpuVoltOffset * 1000));
entry++;
}
}
R_SUCCEED();
};
Result MemFreqPllmLimit(u32 *ptr);
Result MemVoltHandler(u32 *ptr); // Used for Erista MEM Vdd2 + EMC Vddq or Mariko MEM Vdd2
template <typename T>
Result MemMtcCustomizeTable(T *dst, T *src)
{
constexpr u32 mtc_magic = std::is_same_v<T, MarikoMtcTable> ? MARIKO_MTC_MAGIC : ERISTA_MTC_MAGIC;
R_UNLESS(src->rev == mtc_magic, ldr::ResultInvalidMtcMagic());
constexpr u32 ZERO_VAL = UINT32_MAX;
// Skip params from dvfs_ver to clock_src;
for (size_t offset = offsetof(T, clk_src_emc); offset < sizeof(T); offset += sizeof(u32))
{
u32 *src_ent = reinterpret_cast<u32 *>(reinterpret_cast<size_t>(src) + offset);
u32 *dst_ent = reinterpret_cast<u32 *>(reinterpret_cast<size_t>(dst) + offset);
u32 src_val = *src_ent;
if (src_val)
{
PATCH_OFFSET(dst_ent, src_val == ZERO_VAL ? 0 : src_val);
}
}
R_SUCCEED();
};
void SafetyCheck();
void Patch(uintptr_t mapped_nso, size_t nso_size);
}

View File

@@ -1,168 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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
namespace ams::ldr::oc::pcv {
typedef struct cvb_coefficients {
s32 c0 = 0;
s32 c1 = 0;
s32 c2 = 0;
s32 c3 = 0;
s32 c4 = 0;
s32 c5 = 0;
} cvb_coefficients;
typedef struct cvb_entry_t {
u64 freq;
cvb_coefficients cvb_dfll_param;
cvb_coefficients cvb_pll_param;
} cvb_entry_t;
static_assert(sizeof(cvb_entry_t) == 0x38);
typedef struct cvb_cpu_dfll_data {
u32 tune0_low;
u32 tune0_high;
u32 tune1_low;
u32 tune1_high;
unsigned int tune_high_min_millivolts;
unsigned int tune_high_margin_millivolts;
unsigned long dvco_calibration_max;
} cvb_cpu_dfll_data;
typedef struct emc_dvb_dvfs_table_t {
u64 freq;
s32 volt[4] = {0};
} emc_dvb_dvfs_table_t;
typedef struct __attribute__((packed)) div_nmp {
u8 divn_shift;
u8 divn_width;
u8 divm_shift;
u8 divm_width;
u8 divp_shift;
u8 divp_width;
u8 override_divn_shift;
u8 override_divm_shift;
u8 override_divp_shift;
} div_nmp;
typedef struct __attribute__((packed)) clk_pll_param {
u32 freq;
u32 input_min;
u32 input_max;
u32 cf_min;
u32 cf_max;
u32 vco_min;
u32 vco_max;
s32 lock_delay;
u32 fixed_rate;
u32 unk_0;
struct div_nmp *div_nmp;
u32 unk_1[4];
void (*unk_fn)(u64* unk_struct); // set_defaults?
} clk_pll_param;
typedef struct __attribute__((packed)) dvfs_rail {
u32 id;
u32 unk_0[5];
u32 freq;
u32 unk_1[8];
u32 unk_flag;
u32 min_mv;
u32 step_mv;
u32 max_mv;
u32 unk_2[11];
} dvfs_rail;
typedef struct __attribute__((packed)) regulator {
u64 id;
const char* name;
u32 type;
union {
struct {
u32 volt_reg;
u32 step_uv;
u32 min_uv;
u32 default_uv;
u32 max_uv;
u32 unk_0[2];
} type_1;
struct {
u32 unk_0;
u32 step_uv;
u32 unk_1;
u32 min_uv;
u32 max_uv;
u32 unk_2;
u32 default_uv;
} type_2_3;
};
u32 unk_x[60];
} regulator;
static_assert(sizeof(regulator) == 0x120);
constexpr u32 CpuClkOSLimit = 1785'000;
constexpr u32 EmcClkOSLimit = 1600'000;
#define R_SKIP() R_SUCCEED()
// Count 32 / Index 31 is reserved to be empty
constexpr size_t DvfsTableEntryCount = 32;
constexpr size_t DvfsTableEntryLimit = DvfsTableEntryCount - 1;
template<typename T>
size_t GetDvfsTableEntryCount(T* table_head) {
using NT = std::remove_const_t<std::remove_volatile_t<T>>;
auto is_empty = [](NT* entry) {
uint8_t* m = reinterpret_cast<uint8_t *>(entry);
for (size_t i = 0; i < sizeof(NT); i++) {
if (*(m + i))
return false;
}
return true;
};
NT* table = const_cast<NT *>(table_head);
size_t count = 0;
while (count < DvfsTableEntryLimit) {
if (is_empty(table++)) {
return count;
}
count++;
}
return DvfsTableEntryLimit;
}
template<typename T>
T* GetDvfsTableLastEntry(T* table_head) {
using NT = std::remove_const_t<std::remove_volatile_t<T>>;
NT* table = const_cast<NT *>(table_head);
size_t count = GetDvfsTableEntryCount(table_head);
if (!count) {
return nullptr;
}
size_t index = count - 1;
return table + index;
}
}

View File

@@ -1,322 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 "../mtc_timing_value.hpp"
namespace ams::ldr::oc::pcv::erista {
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());
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.eristaCpuDvfsTable)->freq);
R_SUCCEED();
}
Result GpuVmin(u32 *ptr) {
if (!C.eristaGpuVmin)
R_SKIP();
PATCH_OFFSET(ptr, (int)C.eristaGpuVmin);
R_SUCCEED();
}
Result CpuVoltRange(u32 *ptr) {
u32 min_volt_got = *(ptr - 1);
for (const auto &mv : CpuMinVolts) {
if (min_volt_got != mv)
continue;
if (!C.eristaCpuMaxVolt)
R_SKIP();
PATCH_OFFSET(ptr, C.eristaCpuMaxVolt);
R_SUCCEED();
}
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
Result CpuVoltDfll(u32* ptr) {
cvb_cpu_dfll_data *entry = reinterpret_cast<cvb_cpu_dfll_data *>(ptr);
R_UNLESS(entry->tune0_low == 0x152f01, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry());
if(!C.eristaCpuUV) {
R_SKIP();
}
PATCH_OFFSET(&(entry->dvco_calibration_max), 0x1C);
PATCH_OFFSET(&(entry->tune1_high), 0x10);
PATCH_OFFSET(&(entry->tune_high_margin_millivolts), 0xc);
switch(C.eristaCpuUV) {
case 1:
PATCH_OFFSET(&(entry->tune0_high), 0xFFFF); //process_id 0 // EOS UV1
PATCH_OFFSET(&(entry->tune1_high), 0x027007FF);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_high), 0x0000EFFF); //process_id 1 // EOS Uv2
PATCH_OFFSET(&(entry->tune1_high), 0x027407FF);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_high), 0x0000DFFF); //process_id 0 // EOS UV3
PATCH_OFFSET(&(entry->tune1_high), 0x027807FF);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_high), 0x0000DFDF); //process_id 1 // EOS Uv4
PATCH_OFFSET(&(entry->tune1_high), 0x027A07FF);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_high), 0x0000CFDF); // EOS UV5
PATCH_OFFSET(&(entry->tune1_high), 0x037007FF);
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, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[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:
case 3:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTableHigh)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.eristaGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(asm_pattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(asm_pattern[1], max_clock >> 16), rd)};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllLimit(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();
}
void MemMtcTableAutoAdjust(EristaMtcTable *table) {
if (C.mtcConf != AUTO_ADJ)
return;
using namespace pcv::erista;
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
TABLE->burst_regs.PARAM = VALUE; \
TABLE->shadow_regs_ca_train.PARAM = VALUE; \
TABLE->shadow_regs_quse_train.PARAM = VALUE; \
TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
/* Primary timings. */
// WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tCK_avg));
WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(tRAS));
WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb));
/* Secondary timings. */
WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD));
WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb));
WRITE_PARAM_ALL_REG(table, emc_r2w, R2W);
WRITE_PARAM_ALL_REG(table, emc_w2r, W2R);
WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW);
WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab));
WRITE_PARAM_ALL_REG(table, emc_tppd, tPPD);
WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW));
WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC));
WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR));
WRITE_PARAM_ALL_REG(table, emc_tcke, MAX(4u, GET_CYCLE_CEIL(7.5)));
WRITE_PARAM_ALL_REG(table, emc_txsr, GET_CYCLE_CEIL(tXSR));
WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(tRTP));
WRITE_PARAM_ALL_REG(table, emc_w2p, WTP);
WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tXP));
WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tXP));
constexpr u32 MC_ARB_DIV = 4;
constexpr u32 MC_ARB_SFA = 2;
table->burst_mc_regs.mc_emem_arb_timing_rcd = u32(CEIL(GET_CYCLE_CEIL(tRCD) / double(MC_ARB_DIV))) - 2;
table->burst_mc_regs.mc_emem_arb_timing_rp = u32(CEIL(GET_CYCLE_CEIL(tRPpb) / double(MC_ARB_DIV))) - 1 + MC_ARB_SFA;
table->burst_mc_regs.mc_emem_arb_timing_rc = u32(CEIL(GET_CYCLE_CEIL(tRC) / double(MC_ARB_DIV))) - 1;
table->burst_mc_regs.mc_emem_arb_timing_ras = u32(CEIL(GET_CYCLE_CEIL(tRAS) / double(MC_ARB_DIV))) - 2;
table->burst_mc_regs.mc_emem_arb_timing_faw = u32(CEIL(GET_CYCLE_CEIL(tFAW) / double(MC_ARB_DIV))) - 1;
table->burst_mc_regs.mc_emem_arb_timing_rrd = u32(CEIL(GET_CYCLE_CEIL(tRRD) / double(MC_ARB_DIV))) - 1;
table->burst_mc_regs.mc_emem_arb_timing_rap2pre = u32(CEIL(GET_CYCLE_CEIL(tRTP) / double(MC_ARB_DIV)));
table->burst_mc_regs.mc_emem_arb_timing_r2w = u32(CEIL(R2W / double(MC_ARB_DIV)));
table->burst_mc_regs.mc_emem_arb_timing_w2r = u32(CEIL(W2R / double(MC_ARB_DIV)));
#undef GET_CYCLE_CEIL
}
Result MemFreqMtcTable(u32 *ptr) {
if(C.eristaEmcMaxClock != EmcClkOSLimit) {
u32 khz_list[] = {1600000, 1331200, 1065600, 800000, 665600, 408000, 204000, 102000, 68000, 40800};
u32 khz_list_size = sizeof(khz_list) / sizeof(u32);
// Generate list for mtc table pointers
EristaMtcTable *table_list[khz_list_size];
for (u32 i = 0; i < khz_list_size; i++) {
u8 *table = reinterpret_cast<u8 *>(ptr) - offsetof(EristaMtcTable, rate_khz) - i * sizeof(EristaMtcTable);
table_list[i] = reinterpret_cast<EristaMtcTable *>(table);
R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable());
R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
}
if (C.eristaEmcMaxClock <= EmcClkOSLimit)
R_SKIP();
// Make room for new mtc table, discarding useless 40.8 MHz table
// 40800 overwritten by 68000, ..., 1331200 overwritten by 1600000, leaving table_list[0] not overwritten
for (u32 i = khz_list_size - 1; i > 0; i--)
std::memcpy(static_cast<void *>(table_list[i]), static_cast<void *>(table_list[i - 1]), sizeof(EristaMtcTable));
MemMtcTableAutoAdjust(table_list[0]);
PATCH_OFFSET(ptr, C.eristaEmcMaxClock);
// Handle customize table replacement
// if (C.mtcConf == CUSTOMIZED_ALL) {
// MemMtcCustomizeTable(table_list[0], const_cast<EristaMtcTable *>(C.eristaMtcTable));
//}
R_SUCCEED();
} else {
R_SUCCEED(); // Skip changing table on default freq
}
}
Result MemFreqMax(u32 *ptr) {
if (C.eristaEmcMaxClock <= EmcClkOSLimit)
R_SKIP();
PATCH_OFFSET(ptr, C.eristaEmcMaxClock);
R_SUCCEED();
}
// Result MemFreqDvbTable(u32* ptr) {
// emc_dvb_dvfs_table_t* default_end = reinterpret_cast<emc_dvb_dvfs_table_t *>(ptr);
// emc_dvb_dvfs_table_t* new_start = default_end + 1;
// // Validate existing table
// void* mem_dvb_table_head = reinterpret_cast<u8 *>(new_start) - sizeof(EmcDvbTableDefault);
// bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0;
// R_UNLESS(validated, ldr::ResultInvalidDvbTable());
// if (C.eristaEmcMaxClock <= EmcClkOSLimit)
// R_SKIP();
// int32_t voltAdd = 25*C.EmcDvbShift;
// #define DVB_VOLT(zero, one, two) std::min(zero+voltAdd, 1050), std::min(one+voltAdd, 1025), std::min(two+voltAdd, 1000),
// if (C.marikoEmcMaxClock < 1862400) {
// std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t));
// } else if (C.marikoEmcMaxClock < 2131200){
// emc_dvb_dvfs_table_t oc_table = { 1862400, { 950, 925, 900, } };
// std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
// } else if (C.marikoEmcMaxClock < 2227000){
// emc_dvb_dvfs_table_t oc_table = { 2131200, { 975, 950, 925, } };
// std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
// } else {
// emc_dvb_dvfs_table_t oc_table = { 2227000, { DVB_VOLT(1000, 975, 950) } };
// std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
// }
// new_start->freq = C.eristaEmcMaxClock;
// /* 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
// */
// R_SUCCEED();
// }
void Patch(uintptr_t mapped_nso, size_t nso_size) {
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<false>, 1, nullptr, CpuCvbDefaultMaxFreq},
{"CPU Volt Limit", &CpuVoltRange, 13, nullptr, CpuVoltOfficial },
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, 0xFFEAD0FF },
{"GPU Freq Table", GpuFreqCvbTable<false>, 1, nullptr, GpuCvbDefaultMaxFreq},
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn},
{"GPU Freq PLL", &GpuFreqPllLimit, 1, nullptr, GpuClkPllLimit},
{"MEM Freq Mtc", &MemFreqMtcTable, 0, nullptr, EmcClkOSLimit},
{"MEM Freq Max", &MemFreqMax, 0, nullptr, EmcClkOSLimit},
{"MEM Freq PLLM", &MemFreqPllmLimit, 2, nullptr, EmcClkPllmLimit},
// {"MEM Freq Dvb", &MemFreqDvbTable, 1, nullptr, EmcClkOSLimit},
{"MEM Volt", &MemVoltHandler, 2, nullptr, MemVoltHOS},
{"GPU Vmin", &GpuVmin, 0, nullptr, gpuVmin},
};
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()))
CRASH(entry.description);
}
}
}

View File

@@ -1,565 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* Copyright (c) 2023 hanai3Bi
*
* 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 "../mtc_timing_value.hpp"
namespace ams::ldr::oc::pcv::mariko
{
Result GpuVmin(u32 *ptr)
{
if (!C.marikoGpuVmin)
R_SKIP();
PATCH_OFFSET(ptr, (int)C.marikoGpuVmin);
R_SUCCEED();
}
Result GpuVmax(u32 *ptr)
{
if (!C.marikoGpuVmax)
R_SKIP();
PATCH_OFFSET(ptr, (int)C.marikoGpuVmax);
R_SUCCEED();
}
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.marikoCpuUV)
{
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTableSLT)->freq);
} else {
PATCH_OFFSET(ptr, GetDvfsTableLastEntry(C.marikoCpuDvfsTable)->freq);
}
R_SUCCEED();
}
Result CpuVoltRange(u32 *ptr)
{
u32 min_volt_got = *(ptr - 1);
for (const auto &mv : CpuMinVolts)
{
if (min_volt_got != mv)
continue;
if (!C.marikoCpuMaxVolt)
R_SKIP();
PATCH_OFFSET(ptr, C.marikoCpuMaxVolt);
// Patch vmin for slt
if (C.marikoCpuUV)
{
if (*(ptr - 5) == 620)
{
PATCH_OFFSET((ptr - 5), C.marikoCpuVmin);
}
if (*(ptr - 1) == 620)
{
PATCH_OFFSET((ptr - 1), 600);
}
}
R_SUCCEED();
}
R_THROW(ldr::ResultInvalidCpuMinVolt());
}
Result CpuVoltDfll(u32 *ptr)
{
cvb_cpu_dfll_data *entry = reinterpret_cast<cvb_cpu_dfll_data *>(ptr);
R_UNLESS(entry->tune0_low == 0x0000FFCF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune0_high == 0x00000000, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_low == 0x012207FF, ldr::ResultInvalidCpuVoltDfllEntry());
R_UNLESS(entry->tune1_high == 0x03FFF7FF, ldr::ResultInvalidCpuVoltDfllEntry());
switch (C.marikoCpuUV)
{
case 0:
break;
case 1:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FF90); // process_id 0 // EOS UV1
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x00000000);
break;
case 2:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FF92); /// EOS Uv2
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x00000000);
break;
case 3:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FF9A); // EOS UV3
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x00000000);
break;
case 4:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FFA2); // EOS Uv4
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x00000000);
break;
case 5:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV5
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x022217FF);
break;
case 6:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x024417FF);
break;
case 7:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x026617FF);
break;
case 8:
PATCH_OFFSET(&(entry->tune0_low), 0x0000FFFF); // EOS UV6
PATCH_OFFSET(&(entry->tune0_high), 0x0000FFFF);
PATCH_OFFSET(&(entry->tune1_low), 0x021107FF);
PATCH_OFFSET(&(entry->tune1_high), 0x028817FF);
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, asm_pattern[0]) && asm_compare_no_rd(ins2, asm_pattern[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:
case 3:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTableHiOPT)->freq;
break;
default:
max_clock = GetDvfsTableLastEntry(C.marikoGpuDvfsTable)->freq;
break;
}
u32 asm_patch[2] = {
asm_set_rd(asm_set_imm16(asm_pattern[0], max_clock), rd),
asm_set_rd(asm_set_imm16(asm_pattern[1], max_clock >> 16), rd)};
PATCH_OFFSET(ptr32, asm_patch[0]);
PATCH_OFFSET(ptr32 + 1, asm_patch[1]);
R_SUCCEED();
}
Result GpuFreqPllLimit(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();
}
/* Get RAM vendor data, ty b0rd2death! */
/* Note: I know this is horrible but I don't care atm. */
bool IsMicron()
{
u64 packed_version;
splGetConfig((SplConfigItem)2, &packed_version);
switch (packed_version)
{
case 11:
case 15:
case 25:
case 26:
case 27:
case 32:
case 33:
case 34:
/* RAM is Micron. */
return true;
default:
/* Not Micron. */
return false;
}
}
void MemMtcTableAutoAdjust(MarikoMtcTable *table)
{
/* Official Tegra X1 TRM, sign up for nvidia developer program (free) to download:
* https://developer.nvidia.com/embedded/dlc/tegra-x1-technical-reference-manual
* Section 18.11: MC Registers
*
* Retail Mariko: 200FBGA 16Gb DDP LPDDR4X SDRAM x 2
* x16/Ch, 1Ch/die, Double-die, 2Ch, 1CS(rank), 8Gb density per die
* 64Mb x 16DQ x 8banks x 2channels = 2048MB (x32DQ) per package
*
* Devkit Mariko: 200FBGA 32Gb DDP LPDDR4X SDRAM x 2
* x16/Ch, 1Ch/die, Quad-die, 2Ch, 2CS(rank), 8Gb density per die
* X1+ EMC can R/W to both ranks at the same time, resulting in doubled DQ
* 64Mb x 32DQ x 8banks x 2channels = 4096MB (x64DQ) per package
*
* If you have access to LPDDR4(X) specs or datasheets (from manufacturers or Google),
* you'd better calculate timings yourself rather than relying on following algorithm.
*/
if (C.mtcConf != AUTO_ADJ)
{
return;
}
#define WRITE_PARAM_BURST_REG(TABLE, PARAM, VALUE) TABLE->burst_regs.PARAM = VALUE;
#define WRITE_PARAM_CA_TRAIN_REG(TABLE, PARAM, VALUE) TABLE->shadow_regs_ca_train.PARAM = VALUE;
#define WRITE_PARAM_RDWR_TRAIN_REG(TABLE, PARAM, VALUE) TABLE->shadow_regs_rdwr_train.PARAM = VALUE;
#define WRITE_PARAM_ALL_REG(TABLE, PARAM, VALUE) \
WRITE_PARAM_BURST_REG(TABLE, PARAM, VALUE) \
WRITE_PARAM_CA_TRAIN_REG(TABLE, PARAM, VALUE) \
WRITE_PARAM_RDWR_TRAIN_REG(TABLE, PARAM, VALUE)
#define GET_CYCLE_CEIL(PARAM) u32(CEIL(double(PARAM) / tCK_avg))
WRITE_PARAM_ALL_REG(table, emc_rc, GET_CYCLE_CEIL(tRC));
WRITE_PARAM_ALL_REG(table, emc_rfc, GET_CYCLE_CEIL(tRFCab));
WRITE_PARAM_ALL_REG(table, emc_rfcpb, GET_CYCLE_CEIL(tRFCpb));
WRITE_PARAM_ALL_REG(table, emc_ras, GET_CYCLE_CEIL(tRAS));
WRITE_PARAM_ALL_REG(table, emc_rp, GET_CYCLE_CEIL(tRPpb));
WRITE_PARAM_ALL_REG(table, emc_r2p, GET_CYCLE_CEIL(tRTP));
WRITE_PARAM_ALL_REG(table, emc_rd_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_wr_rcd, GET_CYCLE_CEIL(tRCD));
WRITE_PARAM_ALL_REG(table, emc_rrd, GET_CYCLE_CEIL(tRRD));
WRITE_PARAM_ALL_REG(table, emc_refresh, REFRESH);
WRITE_PARAM_ALL_REG(table, emc_pre_refresh_req_cnt, REFRESH / 4);
WRITE_PARAM_ALL_REG(table, emc_r2w, R2W);
WRITE_PARAM_ALL_REG(table, emc_w2r, W2R);
WRITE_PARAM_ALL_REG(table, emc_w2p, WTP);
/* May or may not have to be patched in Micron; let's skip for now. */
if (!IsMicron())
{
WRITE_PARAM_ALL_REG(table, emc_pdex2wr, GET_CYCLE_CEIL(tXP));
WRITE_PARAM_ALL_REG(table, emc_pdex2rd, GET_CYCLE_CEIL(tXP));
}
WRITE_PARAM_ALL_REG(table, emc_txsr, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe));
WRITE_PARAM_ALL_REG(table, emc_txsrdll, MIN(GET_CYCLE_CEIL(tXSR), (u32)0x3fe));
WRITE_PARAM_ALL_REG(table, emc_tckesr, GET_CYCLE_CEIL(tSR));
WRITE_PARAM_ALL_REG(table, emc_tfaw, GET_CYCLE_CEIL(tFAW));
WRITE_PARAM_ALL_REG(table, emc_trpab, GET_CYCLE_CEIL(tRPab));
WRITE_PARAM_ALL_REG(table, emc_trefbw, REFBW);
/* Worth replacing with l4t dumps at some point. */
// Burst MC Regs
#define WRITE_PARAM_BURST_MC_REG(TABLE, PARAM, VALUE) TABLE->burst_mc_regs.PARAM = VALUE;
constexpr u32 MC_ARB_DIV = 4;
constexpr u32 MC_ARB_SFA = 2;
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_cfg, C.marikoEmcMaxClock / (33.3 * 1000) / MC_ARB_DIV); // CYCLES_PER_UPDATE: The number of mcclk cycles per deadline timer update
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rcd, CEIL(GET_CYCLE_CEIL(tRCD) / MC_ARB_DIV) - 2)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rp, CEIL(GET_CYCLE_CEIL(tRPpb) / MC_ARB_DIV) - 1 + MC_ARB_SFA)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rc, CEIL(GET_CYCLE_CEIL(tRC) / MC_ARB_DIV) - 1)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_ras, CEIL(GET_CYCLE_CEIL(tRAS) / MC_ARB_DIV) - 2)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_faw, CEIL(GET_CYCLE_CEIL(tFAW) / MC_ARB_DIV) - 1)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rrd, CEIL(GET_CYCLE_CEIL(tRRD) / MC_ARB_DIV) - 1)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rap2pre, CEIL(GET_CYCLE_CEIL(tRTP) / MC_ARB_DIV))
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_wap2pre, CEIL((WTP) / MC_ARB_DIV))
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2r, CEIL(table->burst_regs.emc_rext / MC_ARB_DIV) - 1 + MC_ARB_SFA)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_r2w, CEIL((R2W) / MC_ARB_DIV) - 1 + MC_ARB_SFA)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_w2r, CEIL((W2R) / MC_ARB_DIV) - 1 + MC_ARB_SFA)
WRITE_PARAM_BURST_MC_REG(table, mc_emem_arb_timing_rfcpb, CEIL(GET_CYCLE_CEIL(tRFCpb) / MC_ARB_DIV))
}
void MemMtcPllmbDivisor(MarikoMtcTable *table)
{
// Calculate DIVM and DIVN (clock divisors)
// Common PLL oscillator is 38.4 MHz
// PLLMB_OUT = 38.4 MHz / PLLLMB_DIVM * PLLMB_DIVN
typedef struct
{
u8 numerator : 4;
u8 denominator : 4;
} pllmb_div;
constexpr pllmb_div div[] = {
{3, 4}, {2, 3}, {1, 2}, {1, 3}, {1, 4}, {0, 2}};
constexpr u32 pll_osc_in = 38'400;
u32 divm{}, divn{};
const u32 remainder = C.marikoEmcMaxClock % pll_osc_in;
for (const auto &index : div)
{
// Round down
if (remainder >= pll_osc_in * index.numerator / index.denominator)
{
divm = index.denominator;
divn = C.marikoEmcMaxClock / pll_osc_in * divm + index.numerator;
break;
}
}
table->pllmb_divm = divm;
table->pllmb_divn = divn;
}
Result MemFreqMtcTable(u32 *ptr)
{
u32 khz_list[] = {1600000, 1331200, 204000};
u32 khz_list_size = sizeof(khz_list) / sizeof(u32);
// Generate list for mtc table pointers
MarikoMtcTable *table_list[khz_list_size];
for (u32 i = 0; i < khz_list_size; i++)
{
u8 *table = reinterpret_cast<u8 *>(ptr) - offsetof(MarikoMtcTable, rate_khz) - i * sizeof(MarikoMtcTable);
table_list[i] = reinterpret_cast<MarikoMtcTable *>(table);
R_UNLESS(table_list[i]->rate_khz == khz_list[i], ldr::ResultInvalidMtcTable());
R_UNLESS(table_list[i]->rev == MTC_TABLE_REV, ldr::ResultInvalidMtcTable());
}
if (C.marikoEmcMaxClock <= EmcClkOSLimit)
R_SKIP();
MarikoMtcTable *table_alt = table_list[1], *table_max = table_list[0];
MarikoMtcTable *tmp = new MarikoMtcTable;
// Copy unmodified 1600000 table to tmp
std::memcpy(reinterpret_cast<void *>(tmp), reinterpret_cast<void *>(table_max), sizeof(MarikoMtcTable));
// Adjust max freq mtc timing parameters with reference to 1331200 table
MemMtcTableAutoAdjust(table_max);
MemMtcPllmbDivisor(table_max);
// Overwrite 13312000 table with unmodified 1600000 table copied back
std::memcpy(reinterpret_cast<void *>(table_alt), reinterpret_cast<void *>(tmp), sizeof(MarikoMtcTable));
delete tmp;
PATCH_OFFSET(ptr, C.marikoEmcMaxClock);
// Handle customize table replacement
// if (C.mtcConf == CUSTOMIZED_ALL) {
// MemMtcCustomizeTable(table_list[0], reinterpret_cast<MarikoMtcTable *>(reinterpret_cast<u8 *>(C.marikoMtcTable)));
// }
R_SUCCEED();
}
Result MemFreqDvbTable(u32 *ptr)
{
emc_dvb_dvfs_table_t *default_end = reinterpret_cast<emc_dvb_dvfs_table_t *>(ptr);
emc_dvb_dvfs_table_t *new_start = default_end + 1;
// Validate existing table
void *mem_dvb_table_head = reinterpret_cast<u8 *>(new_start) - sizeof(EmcDvbTableDefault);
bool validated = std::memcmp(mem_dvb_table_head, EmcDvbTableDefault, sizeof(EmcDvbTableDefault)) == 0;
R_UNLESS(validated, ldr::ResultInvalidDvbTable());
if (C.marikoEmcMaxClock <= EmcClkOSLimit)
R_SKIP();
int32_t voltAdd = 25 * C.EmcDvbShift;
#define DVB_VOLT(zero, one, two) std::min(zero + voltAdd, 1050), std::min(one + voltAdd, 1025), std::min(two + voltAdd, 1000),
if (C.marikoEmcMaxClock < 1862400)
{
std::memcpy(new_start, default_end, sizeof(emc_dvb_dvfs_table_t));
}
else if (C.marikoEmcMaxClock < 2131200)
{
emc_dvb_dvfs_table_t oc_table = {1862400, {
700,
675,
650,
}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
else if (C.marikoEmcMaxClock < 2400000)
{
emc_dvb_dvfs_table_t oc_table = {2131200, {
725,
700,
675,
}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
else if (C.marikoEmcMaxClock < 2665600)
{
emc_dvb_dvfs_table_t oc_table = {2400000, {DVB_VOLT(750, 725, 700)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
else if (C.marikoEmcMaxClock < 2931200)
{
emc_dvb_dvfs_table_t oc_table = {2665600, {DVB_VOLT(775, 750, 725)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
else if (C.marikoEmcMaxClock < 3200000)
{
emc_dvb_dvfs_table_t oc_table = {2931200, {DVB_VOLT(800, 775, 750)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
else
{
emc_dvb_dvfs_table_t oc_table = {3200000, {DVB_VOLT(800, 800, 775)}};
std::memcpy(new_start, &oc_table, sizeof(emc_dvb_dvfs_table_t));
}
new_start->freq = C.marikoEmcMaxClock;
/* 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
*/
R_SUCCEED();
}
Result MemFreqMax(u32 *ptr)
{
if (C.marikoEmcMaxClock <= EmcClkOSLimit)
R_SKIP();
PATCH_OFFSET(ptr, C.marikoEmcMaxClock);
R_SUCCEED();
}
Result I2cSet_U8(I2cDevice dev, u8 reg, u8 val)
{
struct
{
u8 reg;
u8 val;
} __attribute__((packed)) cmd;
I2cSession _session;
Result res = i2cOpenSession(&_session, dev);
if (R_FAILED(res))
return res;
cmd.reg = reg;
cmd.val = val;
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();
I2cSet_U8(I2cDevice_Max77812_2, 0x25, (emc_uv - uv_min) / uv_step);
i2cExit();
R_SUCCEED();
}
void Patch(uintptr_t mapped_nso, size_t nso_size)
{
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 Limit", &CpuVoltRange, 13, nullptr, CpuVoltOfficial},
{"CPU Volt Dfll", &CpuVoltDfll, 1, nullptr, 0x0000FFCF},
{"GPU Freq Table", GpuFreqCvbTable<true>, 1, nullptr, GpuCvbDefaultMaxFreq},
{"GPU Freq Asm", &GpuFreqMaxAsm, 2, &GpuMaxClockPatternFn},
{"GPU Freq PLL", &GpuFreqPllLimit, 0, nullptr, GpuClkPllLimit},
{"MEM Freq Mtc", &MemFreqMtcTable, 0, 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},
{"GPU Vmin", &GpuVmin, 0, nullptr, gpuVmin},
{"GPU Vmax", &GpuVmax, 0, nullptr, gpuVmax},
};
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()))
CRASH(entry.description);
}
}
}

View File

@@ -1,128 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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 "ptm.hpp"
namespace ams::ldr::oc::ptm {
Result CpuPtmBoost(perf_conf_entry* entry) {
if (!C.commonCpuBoostClock)
R_SUCCEED();
u32 cpuPtmBoostNew = C.commonCpuBoostClock * 1000;
PATCH_OFFSET(&(entry->cpu_freq_1), cpuPtmBoostNew);
PATCH_OFFSET(&(entry->cpu_freq_2), cpuPtmBoostNew);
R_SUCCEED();
}
Result MemPtm(perf_conf_entry* entry) {
PATCH_OFFSET(&(entry->emc_freq_1), memPtmLimit);
PATCH_OFFSET(&(entry->emc_freq_2), memPtmLimit);
R_SUCCEED();
}
bool PtmEntryIsValid(perf_conf_entry* entry) {
return (entry->cpu_freq_1 == entry->cpu_freq_2 &&
entry->gpu_freq_1 == entry->gpu_freq_2 &&
entry->emc_freq_1 == entry->emc_freq_2);
}
bool PtmTablePatternFn(u32* ptr) {
perf_conf_entry* entry = reinterpret_cast<perf_conf_entry *>(ptr);
if (!PtmEntryIsValid(entry))
return false;
return entry->cpu_freq_1 == cpuPtmDefault;
}
void Patch(uintptr_t mapped_nso, size_t nso_size) {
perf_conf_entry* confTable = nullptr;
for (uintptr_t ptr = mapped_nso;
ptr <= mapped_nso + nso_size - sizeof(perf_conf_entry) * entryCnt;
ptr += sizeof(u32))
{
u32* ptr32 = reinterpret_cast<u32 *>(ptr);
if (PtmTablePatternFn(ptr32)) {
confTable = reinterpret_cast<perf_conf_entry *>(ptr);
break;
}
}
if (!confTable) {
CRASH("confTable not found!");
}
PatcherEntry<perf_conf_entry> cpuPtmBoostPatch = { "CPU Ptm Boost", &CpuPtmBoost, 2, };
PatcherEntry<perf_conf_entry> memPtmPatch = { "MEM Ptm", &MemPtm, 16, };
#ifdef ATMOSPHERE_IS_STRATOSPHERE
bool isMariko = (spl::GetSocType() == spl::SocType_Mariko);
#else
bool isMariko = true;
#endif
for (u32 i = 0; i < entryCnt; i++) {
perf_conf_entry* entry = confTable + i;
if (!PtmEntryIsValid(entry)) {
LOGGING("@%p", &entry);
CRASH("Invalid ptm confTable entry");
}
switch (entry->cpu_freq_1) {
case cpuPtmBoost:
cpuPtmBoostPatch.Apply(entry);
break;
case cpuPtmDefault:
case cpuPtmDevOC:
break;
default:
LOGGING("%u (0x%08x) @%p", entry->cpu_freq_1, entry->conf_id, &(entry->cpu_freq_1));
CRASH("Unknown CPU Freq");
}
switch (entry->emc_freq_1) {
case memPtmLimit:
case memPtmAlt:
case memPtmClamp:
if (isMariko) {
memPtmPatch.Apply(entry);
}
break;
default:
LOGGING("%u (0x%08x) @%p", entry->emc_freq_1, entry->conf_id, &(entry->emc_freq_2));
CRASH("Unknown MEM Freq");
}
}
LOGGING("%s Count: %zu", cpuPtmBoostPatch.description, cpuPtmBoostPatch.patched_count);
if (R_FAILED(cpuPtmBoostPatch.CheckResult()))
CRASH(cpuPtmBoostPatch.description);
if (isMariko) {
LOGGING("%s Count: %zu", memPtmPatch.description, memPtmPatch.patched_count);
if (R_FAILED(memPtmPatch.CheckResult()))
CRASH(memPtmPatch.description);
}
}
}

View File

@@ -1,47 +0,0 @@
/*
* Copyright (C) Switch-OC-Suite
*
* 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 "../oc_common.hpp"
namespace ams::ldr::oc::ptm {
typedef struct {
u32 conf_id;
u32 cpu_freq_1; // min-max pair?
u32 cpu_freq_2;
u32 gpu_freq_1;
u32 gpu_freq_2;
u32 emc_freq_1;
u32 emc_freq_2;
u32 padding;
} perf_conf_entry;
constexpr u32 entryCnt = 16;
constexpr u32 cpuPtmDefault = 1020'000'000;
constexpr u32 cpuPtmDevOC = 1224'000'000;
constexpr u32 cpuPtmBoost = 1785'000'000;
constexpr u32 memPtmLimit = 1600'000'000;
constexpr u32 memPtmAlt = 1331'200'000;
constexpr u32 memPtmClamp = 1065'600'000;
void Patch(uintptr_t mapped_nso, size_t nso_size);
}

View File

@@ -1,74 +0,0 @@
#!python3
import sys
import os
def file_replace_str(file_path, search_replace_list):
assert file_path
assert search_replace_list
with open(file_path, "r") as f:
content = f.read()
for entry in search_replace_list:
(search, replace) = entry
if search in content:
content = content.replace(search, replace)
else:
assert replace in content, f"Pattern \"{search}\" not found"
with open(file_path, "w") as f:
f.write(content)
dir_path = os.path.dirname(__file__)
os.chdir(dir_path)
os.system("git reset --hard")
ldr_process_creation = os.path.join(dir_path, "ldr_process_creation.cpp")
file_replace_str(ldr_process_creation,
[("""#include "ldr_ro_manager.hpp"
namespace ams::ldr {""",
"""#include "ldr_ro_manager.hpp"
#include "oc/oc_loader.hpp"
namespace ams::ldr {"""),
(""" NsoHeader g_nso_headers[Nso_Count];
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {""",
""" NsoHeader g_nso_headers[Nso_Count];
/* Pcv/Ptm check cache. */
bool g_is_pcv;
bool g_is_ptm;
Result ValidateProgramVersion(ncm::ProgramId program_id, u32 version) {"""),
(""" R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Validate the kernel capabilities. */""",
""" R_UNLESS(meta->aci->program_id <= meta->acid->program_id_max, ldr::ResultInvalidProgramId());
/* Check if nca is pcv or ptm */
g_is_pcv = meta->aci->program_id == ncm::SystemProgramId::Pcv;
g_is_ptm = meta->aci->program_id == ncm::SystemProgramId::Ptm;
/* Validate the kernel capabilities. */"""),
(""" LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
}""",
""" LocateAndApplyIpsPatchesToModule(nso_header->module_id, map_address, nso_size);
/* Apply pcv and ptm patches. */
if (g_is_pcv)
oc::pcv::Patch(map_address, nso_size);
if (g_is_ptm)
oc::ptm::Patch(map_address, nso_size);
}""")])
ldr_meta = os.path.join(dir_path, "ldr_meta.cpp")
file_replace_str(ldr_meta,
[(""" Result ValidateAcidSignature(Meta *meta) {
/* Loader did not check signatures prior to 10.0.0. */""",
""" Result ValidateAcidSignature(Meta *meta) {
R_SUCCEED();
/* Loader did not check signatures prior to 10.0.0. */""")])