Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -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 = {};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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),
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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. */""")])
|
||||
Reference in New Issue
Block a user