Files
Atmosphere/exosphere/program/source/smc/secmon_smc_info.cpp
CTCaer 5a5d9b206b exosphere: automatically adjust dram id if needed
Checks if programmed memory size matches the one from fused dram id.
If not, adjust it properly so PCV can do proper training and not crash.
2026-03-19 16:32:12 +02:00

510 lines
24 KiB
C++

/*
* 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 <exosphere.hpp>
#include "../secmon_error.hpp"
#include "../secmon_map.hpp"
#include "../secmon_misc.hpp"
#include "../secmon_page_mapper.hpp"
#include "../secmon_user_power_management.hpp"
#include "secmon_smc_info.hpp"
#include "secmon_smc_power_management.hpp"
namespace ams::secmon::smc {
namespace {
struct KernelConfiguration {
/* Secure Monitor view. */
using Flags1 = util::BitPack32::Field< 0, 8>;
using Flags0 = util::BitPack32::Field< 8, 8>;
using PhysicalMemorySize = util::BitPack32::Field<16, 2>;
/* Kernel view, from libmesosphere. */
using DebugFillMemory = util::BitPack32::Field<0, 1, bool>;
using EnableUserExceptionHandlers = util::BitPack32::Field<DebugFillMemory::Next, 1, bool>;
using EnableUserPmuAccess = util::BitPack32::Field<EnableUserExceptionHandlers::Next, 1, bool>;
using IncreaseThreadResourceLimit = util::BitPack32::Field<EnableUserPmuAccess::Next, 1, bool>;
using DisableDynamicResourceLimits = util::BitPack32::Field<IncreaseThreadResourceLimit::Next, 1, bool>;
using Reserved5 = util::BitPack32::Field<DisableDynamicResourceLimits::Next, 3, u32>;
using UseSecureMonitorPanicCall = util::BitPack32::Field<Reserved5::Next, 1, bool>;
using Reserved9 = util::BitPack32::Field<UseSecureMonitorPanicCall::Next, 7, u32>;
using MemorySize = util::BitPack32::Field<Reserved9::Next, 2, u32>; /* smc::MemorySize = pkg1::MemorySize */
};
constexpr const pkg1::MemorySize DramIdToMemorySize[fuse::DramId_Count] = {
[fuse::DramId_IcosaSamsung4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IcosaHynix4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IcosaMicron4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaHynix1y4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IcosaSamsung6GB] = pkg1::MemorySize_6GB,
[fuse::DramId_HoagHynix1y4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_AulaHynix1y4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_Deprecated7] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaSansung4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaSamsung8GB] = pkg1::MemorySize_8GB,
[fuse::DramId_IowaHynix4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaMicron4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagSamsung4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagSamsung8GB] = pkg1::MemorySize_8GB,
[fuse::DramId_HoagHynix4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagMicron4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_Deprecated16] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaSamsung1y4GBX] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaSamsung1y8GBX] = pkg1::MemorySize_8GB,
[fuse::DramId_HoagSamsung1y4GBX] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaSamsung1z4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagSamsung1z4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_AulaSamsung1z4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagSamsung1y8GBX] = pkg1::MemorySize_8GB,
[fuse::DramId_AulaSamsung1y4GBX] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaMicron1y4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagMicron1y4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_AulaMicron1y4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_AulaSamsung1y8GBX] = pkg1::MemorySize_8GB,
[fuse::DramId_IowaX1X2Samsung4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagX1X2Samsung4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_AulaX1X2Samsung4GB] = pkg1::MemorySize_4GB,
[fuse::DramId_IowaSamsung4GBY] = pkg1::MemorySize_4GB,
[fuse::DramId_HoagSamsung4GBY] = pkg1::MemorySize_4GB,
[fuse::DramId_AulaSamsung4GBY] = pkg1::MemorySize_4GB,
};
constexpr const pkg1::MemoryMode MemoryModes[] = {
pkg1::MemoryMode_Auto,
pkg1::MemoryMode_4GB,
pkg1::MemoryMode_4GBAppletDev,
pkg1::MemoryMode_4GBSystemDev,
pkg1::MemoryMode_6GB,
pkg1::MemoryMode_6GBAppletDev,
pkg1::MemoryMode_8GB,
};
constexpr bool IsValidMemoryMode(pkg1::MemoryMode mode) {
for (const auto known_mode : MemoryModes) {
if (mode == known_mode) {
return true;
}
}
return false;
}
pkg1::MemoryMode SanitizeMemoryMode(pkg1::MemoryMode mode) {
if (IsValidMemoryMode(mode)) {
return mode;
}
return pkg1::MemoryMode_Auto;
}
pkg1::MemorySize GetAvailableMemorySize(pkg1::MemorySize size) {
return std::min(GetPhysicalMemorySize(), size);
}
pkg1::MemoryMode GetMemoryMode(pkg1::MemoryMode mode) {
/* Sanitize the mode. */
mode = SanitizeMemoryMode(mode);
/* If the mode is auto, construct the memory mode. */
if (mode == pkg1::MemoryMode_Auto) {
return pkg1::MakeMemoryMode(GetPhysicalMemorySize(), pkg1::MemoryArrange_Normal);
} else {
const auto mode_size = GetMemorySize(mode);
const auto mode_arrange = GetMemoryArrange(mode);
const auto size = GetAvailableMemorySize(mode_size);
const auto arrange = (size == mode_size) ? mode_arrange : pkg1::MemoryArrange_Normal;
return pkg1::MakeMemoryMode(size, arrange);
}
}
u32 GetMemoryMode() {
/* Unless development function is enabled, we're 4 GB. */
u32 memory_mode = pkg1::MemoryMode_4GB;
if (const auto &bcd = GetBootConfig().data; bcd.IsDevelopmentFunctionEnabled()) {
memory_mode = GetMemoryMode(bcd.GetMemoryMode());
}
return memory_mode;
}
u32 GetKernelConfiguration() {
pkg1::MemorySize memory_size = pkg1::MemorySize_4GB;
util::BitPack32 value = {};
if (const auto &bcd = GetBootConfig().data; bcd.IsDevelopmentFunctionEnabled()) {
memory_size = GetMemorySize(GetMemoryMode(bcd.GetMemoryMode()));
value.Set<KernelConfiguration::Flags1>(bcd.GetKernelFlags1());
value.Set<KernelConfiguration::Flags0>(bcd.GetKernelFlags0());
}
value.Set<KernelConfiguration::PhysicalMemorySize>(memory_size);
/* Exosphere extensions. */
const auto &sc = GetSecmonConfiguration();
if (!sc.DisableUserModeExceptionHandlers()) {
value.Set<KernelConfiguration::EnableUserExceptionHandlers>(true);
}
if (sc.EnableUserModePerformanceCounterAccess()) {
value.Set<KernelConfiguration::EnableUserPmuAccess>(true);
}
return value.value;
}
fuse::DramId GetDramIdAdjusted() {
const auto dram_id = fuse::GetDramId();
AMS_ABORT_UNLESS(dram_id < fuse::DramId_Count);
const auto fuse_mem_size = DramIdToMemorySize[dram_id];
const auto phys_mem_size = GetPhysicalMemorySize();
AMS_ABORT_UNLESS(fuse_mem_size <= phys_mem_size);
if (fuse_mem_size == phys_mem_size) {
return dram_id;
}
/* Adjust Dram ID to match density/ranks. */
if (GetSocType() == fuse::SocType_Erista) {
return fuse::DramId_IcosaSamsung6GB;
} else { /* fuse::SocType_Mariko */
return fuse::DramId_IowaSamsung1y8GBX;
}
}
constinit u64 g_payload_address = 0;
constinit bool g_set_true_target_firmware = false;
SmcResult GetConfig(SmcArguments &args, bool kern) {
switch (static_cast<ConfigItem>(args.r[1])) {
case ConfigItem::DisableProgramVerification:
args.r[1] = GetBootConfig().signed_data.IsProgramVerificationDisabled();
break;
case ConfigItem::DramId:
args.r[1] = GetDramIdAdjusted(); /* Nintendo: fuse::GetDramId() */
break;
case ConfigItem::SecurityEngineInterruptNumber:
args.r[1] = SecurityEngineUserInterruptId;
break;
case ConfigItem::FuseVersion:
args.r[1] = fuse::GetExpectedFuseVersion(GetTargetFirmware());
break;
case ConfigItem::HardwareType:
args.r[1] = fuse::GetHardwareType();
break;
case ConfigItem::HardwareState:
args.r[1] = fuse::GetHardwareState();
break;
case ConfigItem::IsRecoveryBoot:
args.r[1] = IsRecoveryBoot();
break;
case ConfigItem::DeviceId:
args.r[1] = fuse::GetDeviceId();
break;
case ConfigItem::BootReason:
{
/* This was removed in firmware 4.0.0. */
if (GetTargetFirmware() >= TargetFirmware_4_0_0) {
return SmcResult::InvalidArgument;
}
args.r[1] = GetDeprecatedBootReason();
}
break;
case ConfigItem::MemoryMode:
args.r[1] = GetMemoryMode();
break;
case ConfigItem::IsDevelopmentFunctionEnabled:
args.r[1] = GetSecmonConfiguration().IsDevelopmentFunctionEnabled(kern) || GetBootConfig().data.IsDevelopmentFunctionEnabled();
break;
case ConfigItem::KernelConfiguration:
args.r[1] = GetKernelConfiguration();
break;
case ConfigItem::IsChargerHiZModeEnabled:
args.r[1] = IsChargerHiZModeEnabled();
break;
case ConfigItem::RetailInteractiveDisplayState:
args.r[1] = fuse::GetRetailInteractiveDisplayState();
break;
case ConfigItem::RegulatorType:
args.r[1] = fuse::GetRegulator();
break;
case ConfigItem::DeviceUniqueKeyGeneration:
args.r[1] = fuse::GetDeviceUniqueKeyGeneration();
break;
case ConfigItem::Package2Hash:
{
/* Only allow getting the package2 hash in recovery boot. */
if (!IsRecoveryBoot()) {
return SmcResult::InvalidArgument;
}
/* Get the hash. */
se::Sha256Hash tmp_hash;
GetPackage2Hash(std::addressof(tmp_hash));
/* Copy it out. */
static_assert(sizeof(args) - sizeof(args.r[0]) >= sizeof(tmp_hash));
std::memcpy(std::addressof(args.r[1]), std::addressof(tmp_hash), sizeof(tmp_hash));
}
break;
case ConfigItem::ExosphereApiVersion:
/* Get information about the current exosphere version. */
if (kern || g_set_true_target_firmware) {
args.r[1] = (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) |
(static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) |
(static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) |
(static_cast<u64>(GetKeyGeneration()) << 32) |
(static_cast<u64>(GetTargetFirmware()) << 0);
} else {
return SmcResult::NotInitialized;
}
break;
case ConfigItem::ExosphereNeedsReboot:
/* We are executing, so we aren't in the process of rebooting. */
args.r[1] = 0;
break;
case ConfigItem::ExosphereNeedsShutdown:
/* We are executing, so we aren't in the process of shutting down. */
args.r[1] = 0;
break;
case ConfigItem::ExosphereGitCommitHash:
/* Get information about the current exosphere git commit hash. */
args.r[1] = ATMOSPHERE_GIT_HASH;
break;
case ConfigItem::ExosphereHasRcmBugPatch:
/* Get information about whether this unit has the RCM bug patched. */
args.r[1] = fuse::HasRcmVulnerabilityPatch();
break;
case ConfigItem::ExosphereBlankProdInfo:
/* Get whether this unit should simulate a "blanked" PRODINFO. */
args.r[1] = GetSecmonConfiguration().ShouldUseBlankCalibrationBinary();
break;
case ConfigItem::ExosphereAllowCalWrites:
/* Get whether this unit should allow writing to the calibration partition. */
args.r[1] = (GetEmummcConfiguration().IsEmummcActive() || GetSecmonConfiguration().AllowWritingToCalibrationBinarySysmmc());
break;
case ConfigItem::ExosphereEmummcType:
/* Get what kind of emummc this unit has active. */
/* NOTE: This may return values other than 1 in the future. */
args.r[1] = (GetEmummcConfiguration().IsEmummcActive() ? 1 : 0);
break;
case ConfigItem::ExospherePayloadAddress:
/* Gets the physical address of the reboot payload buffer, if one exists. */
if (g_payload_address != 0) {
args.r[1] = g_payload_address;
} else {
return SmcResult::NotInitialized;
}
break;
case ConfigItem::ExosphereLogConfiguration:
/* Get the log configuration. */
args.r[1] = (static_cast<u64>(static_cast<u8>(secmon::GetLogPort())) << 32) | static_cast<u64>(secmon::GetLogBaudRate());
break;
case ConfigItem::ExosphereForceEnableUsb30:
/* Get whether usb 3.0 should be force-enabled. */
args.r[1] = GetSecmonConfiguration().IsUsb30ForceEnabled();
break;
case ConfigItem::ExosphereSupportedHosVersion:
/* Get information about the supported hos version. */
args.r[1] = (static_cast<u64>(ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR & 0xFF) << 24) |
(static_cast<u64>(ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR & 0xFF) << 16) |
(static_cast<u64>(ATMOSPHERE_SUPPORTED_HOS_VERSION_MICRO & 0xFF) << 8);
break;
case ConfigItem::ExosphereApproximateApiVersion:
/* Get information about the current exosphere version. */
if (!g_set_true_target_firmware) {
args.r[1] = (static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) |
(static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) |
(static_cast<u64>(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) |
(static_cast<u64>(GetKeyGeneration()) << 32) |
(static_cast<u64>(GetTargetFirmware()) << 0);
} else {
return SmcResult::Busy;
}
break;
default:
return SmcResult::InvalidArgument;
}
return SmcResult::Success;
}
SmcResult SetConfig(SmcArguments &args) {
const auto soc_type = GetSocType();
switch (static_cast<ConfigItem>(args.r[1])) {
case ConfigItem::IsChargerHiZModeEnabled:
/* Configure the HiZ mode. */
SetChargerHiZModeEnabled(static_cast<bool>(args.r[3]));
break;
case ConfigItem::ExosphereApiVersion:
if (!g_set_true_target_firmware) {
::ams::secmon::impl::SetTargetFirmware(static_cast<ams::TargetFirmware>(args.r[3] & 0xFFFFFFFF));
g_set_true_target_firmware = true;
} else {
return SmcResult::Busy;
}
break;
case ConfigItem::ExosphereNeedsReboot:
if (soc_type == fuse::SocType_Erista) {
switch (static_cast<UserRebootType>(args.r[3])) {
case UserRebootType_None:
break;
case UserRebootType_ToRcm:
PerformUserRebootToRcm();
break;
case UserRebootType_ToPayload:
PerformUserRebootToPayload();
break;
case UserRebootType_ToFatalError:
PerformUserRebootToFatalError();
break;
case UserRebootType_ByPmic:
PerformUserRebootByPmic();
break;
default:
return SmcResult::InvalidArgument;
}
} else /* if (soc_type == fuse::SocType_Mariko) */ {
switch (static_cast<UserRebootType>(args.r[3])) {
case UserRebootType_ToFatalError:
PerformUserRebootToFatalError();
break;
case UserRebootType_ByPmic:
PerformUserRebootByPmic();
break;
default:
return SmcResult::InvalidArgument;
}
}
break;
case ConfigItem::ExosphereNeedsShutdown:
if (args.r[3] != 0) {
PerformUserShutDown();
}
break;
case ConfigItem::ExospherePayloadAddress:
if (g_payload_address == 0) {
if (secmon::IsPhysicalMemoryAddress(args.r[2])) {
g_payload_address = args.r[2];
} else {
return SmcResult::InvalidArgument;
}
} else {
return SmcResult::Busy;
}
break;
default:
return SmcResult::InvalidArgument;
}
return SmcResult::Success;
}
}
SmcResult SmcGetConfigUser(SmcArguments &args) {
return GetConfig(args, false);
}
SmcResult SmcGetConfigKern(SmcArguments &args) {
return GetConfig(args, true);
}
SmcResult SmcSetConfig(SmcArguments &args) {
return SetConfig(args);
}
/* This is an atmosphere extension smc. */
SmcResult SmcGetEmummcConfig(SmcArguments &args) {
/* Decode arguments. */
const auto mmc = static_cast<EmummcMmc>(args.r[1]);
const uintptr_t user_address = args.r[2];
const uintptr_t user_offset = user_address % 4_KB;
/* Validate arguments. */
/* NOTE: In the future, configuration for non-NAND storage may be implemented. */
SMC_R_UNLESS(mmc == EmummcMmc_Nand, NotSupported);
SMC_R_UNLESS(user_offset + 2 * sizeof(EmummcFilePath) <= 4_KB, InvalidArgument);
/* Get the emummc config. */
const auto &cfg = GetEmummcConfiguration();
static_assert(sizeof(cfg.file_cfg) == sizeof(EmummcFilePath));
static_assert(sizeof(cfg.emu_dir_path) == sizeof(EmummcFilePath));
/* Clear the output. */
constexpr size_t InlineOutputSize = sizeof(args) - sizeof(args.r[0]);
u8 * const inline_output = static_cast<u8 *>(static_cast<void *>(std::addressof(args.r[1])));
std::memset(inline_output, 0, InlineOutputSize);
/* Copy out the configuration. */
{
/* Map the user output page. */
AtmosphereUserPageMapper mapper(user_address);
SMC_R_UNLESS(mapper.Map(), InvalidArgument);
/* Copy the base configuration. */
static_assert(sizeof(cfg.base_cfg) <= InlineOutputSize);
std::memcpy(inline_output, std::addressof(cfg.base_cfg), sizeof(cfg.base_cfg));
/* Copy out type-specific data. */
switch (cfg.base_cfg.type) {
case EmummcType_None:
/* No additional configuration needs to be copied. */
break;
case EmummcType_Partition:
/* Copy the partition config. */
static_assert(sizeof(cfg.base_cfg) + sizeof(cfg.partition_cfg) <= InlineOutputSize);
std::memcpy(inline_output + sizeof(cfg.base_cfg), std::addressof(cfg.partition_cfg), sizeof(cfg.partition_cfg));
break;
case EmummcType_File:
/* Copy the file config. */
SMC_R_UNLESS(mapper.CopyToUser(user_address, std::addressof(cfg.file_cfg), sizeof(cfg.file_cfg)), InvalidArgument);
break;
AMS_UNREACHABLE_DEFAULT_CASE();
}
/* Copy the redirection directory path to the user page. */
SMC_R_UNLESS(mapper.CopyToUser(user_address + sizeof(EmummcFilePath), std::addressof(cfg.emu_dir_path), sizeof(cfg.emu_dir_path)), InvalidArgument);
}
return SmcResult::Success;
}
/* For exosphere's usage. */
pkg1::MemorySize GetPhysicalMemorySize() {
const uintptr_t MC = secmon::MemoryRegionVirtualDeviceMemoryController.GetAddress();
const u32 mem_size = reg::Read(MC + MC_EMEM_CFG) & 0x3FFF;
switch (mem_size >> 10) {
case 4:
default:
return pkg1::MemorySize_4GB;
case 6:
return pkg1::MemorySize_6GB;
case 8:
return pkg1::MemorySize_8GB;
}
}
}