From 6bf283ec2ee8fd51f6cb96e3fb4d2bd7acfd19aa Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 15 May 2020 02:32:17 -0700 Subject: [PATCH] exo2: implement SmcGetConfig --- exosphere2/program/source/secmon_error.cpp | 6 +- .../program/source/secmon_exception_vectors.s | 2 +- exosphere2/program/source/secmon_misc.cpp | 11 + exosphere2/program/source/secmon_misc.hpp | 2 + .../program/source/smc/secmon_smc_handler.cpp | 13 +- .../program/source/smc/secmon_smc_info.cpp | 262 +++++++++++++++++- .../program/source/smc/secmon_smc_info.hpp | 2 +- .../source/smc/secmon_smc_register_access.cpp | 18 +- libraries/config/common.mk | 4 +- .../libexosphere/include/exosphere/fuse.hpp | 58 +++- .../pkg1/pkg1_bootloader_parameters.hpp | 4 +- .../secmon/secmon_emummc_context.hpp | 16 ++ .../secmon/secmon_monitor_context.hpp | 2 + .../libexosphere/source/fuse/fuse_api.cpp | 260 +++++++++++++++-- .../source/fuse/fuse_registers.hpp | 25 ++ 15 files changed, 640 insertions(+), 45 deletions(-) diff --git a/exosphere2/program/source/secmon_error.cpp b/exosphere2/program/source/secmon_error.cpp index a9e6181c5..c05bf4cd7 100644 --- a/exosphere2/program/source/secmon_error.cpp +++ b/exosphere2/program/source/secmon_error.cpp @@ -36,6 +36,8 @@ namespace ams::diag { } *(volatile u32 *)(secmon::MemoryRegionVirtualDevicePmc.GetAddress() + 0x50) = 0x02; *(volatile u32 *)(secmon::MemoryRegionVirtualDevicePmc.GetAddress() + 0x00) = 0x10; + + util::WaitMicroSeconds(1000); } #endif @@ -67,8 +69,8 @@ namespace ams::secmon { *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x0C) = static_cast(temp_reg >> 32); __asm__ __volatile__("mrs %0, elr_el3" : "=r"(temp_reg) :: "memory"); - *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x18) = static_cast(temp_reg >> 0); - *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x1C) = static_cast(temp_reg >> 32); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x10) = static_cast(temp_reg >> 0); + *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x14) = static_cast(temp_reg >> 32); __asm__ __volatile__("mrs %0, far_el3" : "=r"(temp_reg) :: "memory"); *(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x18) = static_cast(temp_reg >> 0); diff --git a/exosphere2/program/source/secmon_exception_vectors.s b/exosphere2/program/source/secmon_exception_vectors.s index ef1ab9f32..2749f9f64 100644 --- a/exosphere2/program/source/secmon_exception_vectors.s +++ b/exosphere2/program/source/secmon_exception_vectors.s @@ -211,7 +211,7 @@ _ZN3ams6secmon25HandleSmcExceptionCore012Ev: /* Restore our core-specific stack. */ ldp x29, x30, [sp], #0x10 - mov x30, sp + mov sp, x30 /* Release our exclusive access to the common smc stack. */ stp x0, x1, [sp, #-0x10]! diff --git a/exosphere2/program/source/secmon_misc.cpp b/exosphere2/program/source/secmon_misc.cpp index d0981d771..eed73b5eb 100644 --- a/exosphere2/program/source/secmon_misc.cpp +++ b/exosphere2/program/source/secmon_misc.cpp @@ -23,11 +23,18 @@ namespace ams::secmon { constinit pkg1::BctParameters g_bct_params = {}; constinit se::Sha256Hash g_package2_hash = {}; + constinit u32 g_deprecated_boot_reason_value = {}; + constinit u8 g_deprecated_boot_reason_state = {}; + } void SaveBootInfo(const pkg1::SecureMonitorParameters &secmon_params) { /* Save the BCT parameters. */ g_bct_params = secmon_params.bct_params; + + /* Save the deprecated boot reason. */ + g_deprecated_boot_reason_value = secmon_params.deprecated_boot_reason_value; + g_deprecated_boot_reason_state = secmon_params.deprecated_boot_reason_state; } bool IsRecoveryBoot() { @@ -52,4 +59,8 @@ namespace ams::secmon { g_package2_hash = hash; } + u32 GetDeprecatedBootReason() { + return (static_cast(g_deprecated_boot_reason_state) << 24) | (g_deprecated_boot_reason_value & 0x00FFFFFF); + } + } diff --git a/exosphere2/program/source/secmon_misc.hpp b/exosphere2/program/source/secmon_misc.hpp index e8b5922ff..f637d0a64 100644 --- a/exosphere2/program/source/secmon_misc.hpp +++ b/exosphere2/program/source/secmon_misc.hpp @@ -29,4 +29,6 @@ namespace ams::secmon { void GetPackage2Hash(se::Sha256Hash *out); void SetPackage2Hash(const se::Sha256Hash &hash); + u32 GetDeprecatedBootReason(); + } \ No newline at end of file diff --git a/exosphere2/program/source/smc/secmon_smc_handler.cpp b/exosphere2/program/source/smc/secmon_smc_handler.cpp index 918824504..ac960fae4 100644 --- a/exosphere2/program/source/smc/secmon_smc_handler.cpp +++ b/exosphere2/program/source/smc/secmon_smc_handler.cpp @@ -128,7 +128,7 @@ namespace ams::secmon::smc { { 0xC4000001, Restriction_SafeModeNotAllowed, SmcSuspendCpu }, { 0x84000002, Restriction_SafeModeNotAllowed, SmcPowerOffCpu }, { 0xC4000003, Restriction_SafeModeNotAllowed, SmcPowerOnCpu }, - { 0xC3000002, Restriction_Normal, SmcGetConfigKern }, + { 0xC3000004, Restriction_Normal, SmcGetConfigKern }, { 0xC3000005, Restriction_Normal, SmcGenerateRandomBytesNonBlocking }, { 0xC3000006, Restriction_Normal, SmcShowError }, { 0xC3000007, Restriction_Normal, SmcSetKernelCarveoutRegion }, @@ -240,6 +240,17 @@ namespace ams::secmon::smc { *(volatile u32 *)(MemoryRegionVirtualDevicePmc.GetAddress() + 0x50) = 0x02; *(volatile u32 *)(MemoryRegionVirtualDevicePmc.GetAddress() + 0x00) = 0x10; + util::WaitMicroSeconds(1000); + } + if (args.r[0] != static_cast(SmcResult::Success)) { + *(volatile u32 *)(MemoryRegionVirtualDebug.GetAddress()) = 0xCCCCCCCC; + *(volatile u32 *)(MemoryRegionVirtualDebug.GetAddress() + 0x10) = static_cast(info.function_id); + for (size_t i = 0; i < sizeof(args) / sizeof(u32); ++i) { + ((volatile u32 *)(MemoryRegionVirtualDebug.GetAddress() + 0x20))[i] = reinterpret_cast(std::addressof(args))[i]; + } + *(volatile u32 *)(MemoryRegionVirtualDevicePmc.GetAddress() + 0x50) = 0x02; + *(volatile u32 *)(MemoryRegionVirtualDevicePmc.GetAddress() + 0x00) = 0x10; + util::WaitMicroSeconds(1000); } #endif diff --git a/exosphere2/program/source/smc/secmon_smc_info.cpp b/exosphere2/program/source/smc/secmon_smc_info.cpp index fba5f75ad..08540e09e 100644 --- a/exosphere2/program/source/smc/secmon_smc_info.cpp +++ b/exosphere2/program/source/smc/secmon_smc_info.cpp @@ -15,18 +15,272 @@ */ #include #include "../secmon_error.hpp" +#include "../secmon_misc.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; + using EnableUserPmuAccess = util::BitPack32::Field; + using IncreaseThreadResourceLimit = util::BitPack32::Field; + using Reserved4 = util::BitPack32::Field; + using UseSecureMonitorPanicCall = util::BitPack32::Field; + using Reserved9 = util::BitPack32::Field; + using MemorySize = util::BitPack32::Field; /* 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_CopperSamsung4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IcosaSamsung6GB] = pkg1::MemorySize_6GB, + [fuse::DramId_CopperHynix4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_CopperMicron4GB] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaX1X2Samsung4GB] = 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_IowaSamsung4GBY] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1y4GBX] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1y8GBX] = pkg1::MemorySize_8GB, + [fuse::DramId_HoagSamsung1y4GBX] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1y4GBY] = pkg1::MemorySize_4GB, + [fuse::DramId_IowaSamsung1y8GBY] = pkg1::MemorySize_8GB, + [fuse::DramId_IowaSamsung1y4GBA] = pkg1::MemorySize_4GB, + [fuse::DramId_FiveSamsung1y8GBX] = pkg1::MemorySize_8GB, + [fuse::DramId_FiveSamsung1y4GBX] = 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 GetPhysicalMemorySize() { + const auto dram_id = fuse::GetDramId(); + AMS_ABORT_UNLESS(dram_id < fuse::DramId_Count); + return DramIdToMemorySize[dram_id]; + } + + 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(bcd.GetKernelFlags1()); + value.Set(bcd.GetKernelFlags0()); + } + + value.Set(memory_size); + + /* Exosphere extensions. */ + const auto &sc = GetSecmonConfiguration(); + + if (!sc.DisableUserModeExceptionHandlers()) { + value.Set(true); + } + + if (sc.EnableUserModePerformanceCounterAccess()) { + value.Set(true); + } + + return value.value; + } + + SmcResult GetConfig(SmcArguments &args, bool kern) { + switch (static_cast(args.r[1])) { + case ConfigItem::DisableProgramVerification: + args.r[1] = GetBootConfig().signed_data.IsProgramVerificationDisabled(); + break; + case ConfigItem::DramId: + args.r[1] = 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::QuestState: + args.r[1] = fuse::GetQuestState(); + 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. */ + args.r[1] = (static_cast(ATMOSPHERE_RELEASE_VERSION_MAJOR & 0xFF) << 56) | + (static_cast(ATMOSPHERE_RELEASE_VERSION_MINOR & 0xFF) << 48) | + (static_cast(ATMOSPHERE_RELEASE_VERSION_MICRO & 0xFF) << 40) | + (static_cast(GetKeyGeneration()) << 32) | + (static_cast(GetTargetFirmware()) << 00); + 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; + default: + return SmcResult::InvalidArgument; + } + + return SmcResult::Success; + } + + } + SmcResult SmcGetConfigUser(SmcArguments &args) { - /* TODO */ - return SmcResult::NotImplemented; + return GetConfig(args, false); } SmcResult SmcGetConfigKern(SmcArguments &args) { - /* TODO */ - return SmcResult::NotImplemented; + return GetConfig(args, true); } SmcResult SmcSetConfig(SmcArguments &args) { diff --git a/exosphere2/program/source/smc/secmon_smc_info.hpp b/exosphere2/program/source/smc/secmon_smc_info.hpp index 907682b8a..26d28e17b 100644 --- a/exosphere2/program/source/smc/secmon_smc_info.hpp +++ b/exosphere2/program/source/smc/secmon_smc_info.hpp @@ -34,7 +34,7 @@ namespace ams::secmon::smc { IsDevelopmentFunctionEnabled = 11, KernelConfiguration = 12, IsChargerHiZModeEnabled = 13, - IsQuest = 14, + QuestState = 14, RegulatorType = 15, DeviceUniqueKeyGeneration = 16, Package2Hash = 17, diff --git a/exosphere2/program/source/smc/secmon_smc_register_access.cpp b/exosphere2/program/source/smc/secmon_smc_register_access.cpp index 0f3e6e08e..b9e8b4d63 100644 --- a/exosphere2/program/source/smc/secmon_smc_register_access.cpp +++ b/exosphere2/program/source/smc/secmon_smc_register_access.cpp @@ -24,9 +24,7 @@ namespace ams::secmon::smc { template constexpr void SetRegisterTableAllowed(std::array &arr, uintptr_t reg) { /* All registers should be four byte aligned. */ - if (reg % sizeof(u32) != 0) { - __builtin_unreachable(); - } + AMS_ASSUME(reg % sizeof(u32) == 0); /* Reduce the register to an index. */ reg /= sizeof(u32); @@ -36,24 +34,18 @@ namespace ams::secmon::smc { const auto mask = (1u << (reg % BITSIZEOF(u8))); /* Check that the permission bit isn't already set. */ - if ((arr[index] & mask) != 0) { - __builtin_unreachable(); - } + AMS_ASSUME((arr[index] & mask) == 0); /* Set the permission bit. */ arr[index] |= mask; /* Ensure that indices are set in sorted order. */ for (auto i = (reg % BITSIZEOF(u8)) + 1; i < 8; ++i) { - if ((arr[index] & (1u << i)) != 0) { - __builtin_unreachable(); - } + AMS_ASSUME((arr[index] & (1u << i)) == 0); } for (auto i = index + 1; i < arr.size(); ++i) { - if (arr[i] != 0) { - __builtin_unreachable(); - } + AMS_ASSUME(arr[i] == 0); } } @@ -72,7 +64,7 @@ namespace ams::secmon::smc { } /* All empty perm table is disallowed. */ - __builtin_unreachable(); + AMS_ASSUME(false); } diff --git a/libraries/config/common.mk b/libraries/config/common.mk index e3a12dd7b..ebe9bcfb8 100644 --- a/libraries/config/common.mk +++ b/libraries/config/common.mk @@ -80,7 +80,9 @@ else export ATMOSPHERE_GIT_REVISION := $(ATMOSPHERE_GIT_BRANCH)-$(shell git rev-parse --short HEAD)-dirty endif -ATMOSPHERE_DEFINES += -DATMOSPHERE_GIT_BRANCH=\"$(ATMOSPHERE_GIT_BRANCH)\" -DATMOSPHERE_GIT_REVISION=\"$(ATMOSPHERE_GIT_REVISION)\" +export ATMOSPHERE_GIT_HASH := $(shell git rev-parse --short=16 HEAD) + +ATMOSPHERE_DEFINES += -DATMOSPHERE_GIT_BRANCH=\"$(ATMOSPHERE_GIT_BRANCH)\" -DATMOSPHERE_GIT_REVISION=\"$(ATMOSPHERE_GIT_REVISION)\" -DATMOSPHERE_GIT_HASH="0x$(ATMOSPHERE_GIT_HASH)" #--------------------------------------------------------------------------------- # Ensure top directory is set. diff --git a/libraries/libexosphere/include/exosphere/fuse.hpp b/libraries/libexosphere/include/exosphere/fuse.hpp index 55efdb334..e6ff9ebb9 100644 --- a/libraries/libexosphere/include/exosphere/fuse.hpp +++ b/libraries/libexosphere/include/exosphere/fuse.hpp @@ -31,21 +31,77 @@ namespace ams::fuse { HardwareType_Undefined = 0xF, }; + enum SocType { + SocType_Erista = 0, + SocType_Mariko = 1, + SocType_Undefined = 0xF, + }; + enum HardwareState { HardwareState_Development = 0, HardwareState_Production = 1, HardwareState_Undefined = 2, }; + enum DramId { + DramId_IcosaSamsung4GB = 0, + DramId_IcosaHynix4GB = 1, + DramId_IcosaMicron4GB = 2, + DramId_CopperSamsung4GB = 3, + DramId_IcosaSamsung6GB = 4, + DramId_CopperHynix4GB = 5, + DramId_CopperMicron4GB = 6, + DramId_IowaX1X2Samsung4GB = 7, + DramId_IowaSansung4GB = 8, + DramId_IowaSamsung8GB = 9, + DramId_IowaHynix4GB = 10, + DramId_IowaMicron4GB = 11, + DramId_HoagSamsung4GB = 12, + DramId_HoagSamsung8GB = 13, + DramId_HoagHynix4GB = 14, + DramId_HoagMicron4GB = 15, + DramId_IowaSamsung4GBY = 16, + DramId_IowaSamsung1y4GBX = 17, + DramId_IowaSamsung1y8GBX = 18, + DramId_HoagSamsung1y4GBX = 19, + DramId_IowaSamsung1y4GBY = 20, + DramId_IowaSamsung1y8GBY = 21, + DramId_IowaSamsung1y4GBA = 22, + DramId_FiveSamsung1y8GBX = 23, + DramId_FiveSamsung1y4GBX = 24, + + DramId_Count, + }; + + enum QuestState { + QuestState_Disabled = 0, + QuestState_Enabled = 1, + }; + void SetRegisterAddress(uintptr_t address); void SetWriteSecureOnly(); void Lockout(); + void Activate(); + void Deactivate(); + void Reload(); + + u32 ReadWord(int address); + u32 GetOdmWord(int index); + DramId GetDramId(); + + void GetEcid(br::BootEcid *out); HardwareType GetHardwareType(); HardwareState GetHardwareState(); + u64 GetDeviceId(); + QuestState GetQuestState(); pmic::Regulator GetRegulator(); - void GetEcid(br::BootEcid *out); + int GetDeviceUniqueKeyGeneration(); + + SocType GetSocType(); + int GetExpectedFuseVersion(TargetFirmware target_fw); + bool HasRcmVulnerabilityPatch(); } \ No newline at end of file diff --git a/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp b/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp index 93f70df18..86eda5279 100644 --- a/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp +++ b/libraries/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp @@ -45,7 +45,9 @@ namespace ams::pkg1 { u32 secmon_start_time; u32 secmon_end_time; BctParameters bct_params; - u8 reserved[0xD8]; + u32 deprecated_boot_reason_value; + u8 deprecated_boot_reason_state; + u8 reserved[0xD3]; u32 bootloader_state; u32 secmon_state; u8 reserved2[0x100]; diff --git a/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp b/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp index 044420e8c..b9c8c47db 100644 --- a/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp +++ b/libraries/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp @@ -45,6 +45,14 @@ namespace ams::secmon { EmummcType type; u32 id; u32 fs_version; + + constexpr bool IsValid() const { + return this->magic == Magic; + } + + constexpr bool IsEmummcActive() const { + return this->IsValid() && this->type != EmummcType_None; + } }; static_assert(util::is_pod::value); static_assert(sizeof(EmummcBaseConfiguration) == 0x10); @@ -66,6 +74,14 @@ namespace ams::secmon { EmummcFileConfiguration file_cfg; }; EmummcFilePath emu_dir_path; + + constexpr bool IsValid() const { + return this->base_cfg.IsValid(); + } + + constexpr bool IsEmummcActive() const { + return this->base_cfg.IsEmummcActive(); + } }; static_assert(util::is_pod::value); static_assert(sizeof(EmummcConfiguration) <= 0x200); diff --git a/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp b/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp index 1aa0b679d..2b3822b00 100644 --- a/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp +++ b/libraries/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp @@ -65,6 +65,8 @@ namespace ams::secmon { constexpr bool EnableUserModePerformanceCounterAccess() const { return (this->flags & SecureMonitorConfigurationFlag_EnableUserModePerformanceCounterAccess) != 0; } constexpr bool ShouldUseBlankCalibrationBinary() const { return (this->flags & SecureMonitorConfigurationFlag_ShouldUseBlankCalibrationBinary) != 0; } constexpr bool AllowWritingToCalibrationBinarySysmmc() const { return (this->flags & SecureMonitorConfigurationFlag_AllowWritingToCalibrationBinarySysmmc) != 0; } + + constexpr bool IsDevelopmentFunctionEnabled(bool for_kern) const { return for_kern ? this->IsDevelopmentFunctionEnabledForKernel() : this->IsDevelopmentFunctionEnabledForUser(); } }; static_assert(util::is_pod::value); static_assert(sizeof(SecureMonitorConfiguration) == 0x80); diff --git a/libraries/libexosphere/source/fuse/fuse_api.cpp b/libraries/libexosphere/source/fuse/fuse_api.cpp index 32d491393..c01286bfe 100644 --- a/libraries/libexosphere/source/fuse/fuse_api.cpp +++ b/libraries/libexosphere/source/fuse/fuse_api.cpp @@ -20,6 +20,11 @@ namespace ams::fuse { namespace { + struct OdmWord2 { + using DeviceUniqueKeyGeneration = util::BitPack32::Field<0, 5, int>; + using Reserved = util::BitPack32::Field<5, 27, int>; + }; + struct OdmWord4 { using HardwareState1 = util::BitPack32::Field<0, 2, int>; using HardwareType1 = util::BitPack32::Field; @@ -52,6 +57,9 @@ namespace ams::fuse { constinit uintptr_t g_register_address = secmon::MemoryRegionPhysicalDeviceFuses.GetAddress(); + constinit bool g_checked_for_rcm_bug_patch = false; + constinit bool g_has_rcm_bug_patch = false; + ALWAYS_INLINE volatile FuseRegisterRegion *GetRegisterRegion() { return reinterpret_cast(g_register_address); } @@ -64,6 +72,92 @@ namespace ams::fuse { return GetRegisterRegion()->chip; } + bool IsIdle() { + return reg::HasValue(GetRegisters().FUSE_FUSECTRL, FUSE_REG_BITS_ENUM(FUSECTRL_STATE, IDLE)); + } + + void WaitForIdle() { + while (!IsIdle()) { /* ... */ } + } + + bool IsNewFuseFormat() { + /* On mariko, this should always be true. */ + if (GetSocType() != SocType_Erista) { + return true; + } + + /* Require that the format version be non-zero in odm4. */ + if (util::BitPack32{GetOdmWord(4)}.Get() == 0) { + return false; + } + + /* Check that odm word 0/1 are fused with the magic values. */ + constexpr u32 NewFuseFormatMagic0 = 0x8E61ECAE; + constexpr u32 NewFuseFormatMagic1 = 0xF2BA3BB2; + + const u32 w0 = GetOdmWord(0); + const u32 w1 = GetOdmWord(1); + + return w0 == NewFuseFormatMagic0 && w1 == NewFuseFormatMagic1; + } + + constexpr u32 CompressLotCode(u32 lot0) { + constexpr int Radix = 36; + constexpr int Count = 5; + constexpr int Width = 6; + constexpr u32 Mask = (1u << Width) - 1; + + u32 compressed = 0; + + for (int i = Count - 1; i >= 0; --i) { + compressed *= Radix; + compressed += (lot0 >> (i * Width)) & Mask; + } + + return compressed; + } + + constexpr const TargetFirmware FuseVersionIncrementFirmwares[] = { + TargetFirmware_10_0_0, + TargetFirmware_9_1_0, + TargetFirmware_9_0_0, + TargetFirmware_8_1_0, + TargetFirmware_7_0_0, + TargetFirmware_6_2_0, + TargetFirmware_6_0_0, + TargetFirmware_5_0_0, + TargetFirmware_4_0_0, + TargetFirmware_3_0_2, + TargetFirmware_3_0_0, + TargetFirmware_2_0_0, + TargetFirmware_1_0_0, + }; + + constexpr inline int NumFuseIncrements = util::size(FuseVersionIncrementFirmwares); + + /* Verify that the fuse version increment list is sorted. */ + static_assert([] { + for (size_t i = 0; i < util::size(FuseVersionIncrementFirmwares) - 1; ++i) { + if (FuseVersionIncrementFirmwares[i] <= FuseVersionIncrementFirmwares[i + 1]) { + return false; + } + } + return true; + }()); + + constexpr int GetExpectedFuseVersionImpl(TargetFirmware target_fw) { + for (int i = 0; i < NumFuseIncrements; ++i) { + if (target_fw >= FuseVersionIncrementFirmwares[i]) { + return NumFuseIncrements - i; + } + } + return 0; + } + + static_assert(GetExpectedFuseVersionImpl(TargetFirmware_10_0_0) == 13); + static_assert(GetExpectedFuseVersionImpl(TargetFirmware_1_0_0) == 1); + static_assert(GetExpectedFuseVersionImpl(static_cast(0)) == 0); + } void SetRegisterAddress(uintptr_t address) { @@ -78,10 +172,84 @@ namespace ams::fuse { reg::Write(GetRegisters().FUSE_DISABLEREGPROGRAM, FUSE_REG_BITS_ENUM(DISABLEREGPROGRAM_DISABLEREGPROGRAM_VAL, ENABLE)); } + u32 ReadWord(int address) { + /* Require that the fuse array be idle. */ + AMS_ABORT_UNLESS(IsIdle()); + + /* Get the registers. */ + volatile auto &FUSE = GetRegisters(); + + /* Write the address to read. */ + reg::Write(FUSE.FUSE_FUSEADDR, address); + + /* Set control to read. */ + reg::ReadWrite(FUSE.FUSE_FUSECTRL, FUSE_REG_BITS_ENUM(FUSECTRL_CMD, READ)); + + /* Wait 1 us. */ + util::WaitMicroSeconds(1); + + /* Wait for the array to be idle. */ + WaitForIdle(); + + return reg::Read(FUSE.FUSE_FUSERDATA); + } + u32 GetOdmWord(int index) { return GetChipRegisters().FUSE_RESERVED_ODM[index]; } + void GetEcid(br::BootEcid *out) { + /* Get the registers. */ + const volatile auto &chip = GetChipRegisters(); + + /* Read the ecid components. */ + const u32 vendor = reg::Read(chip.FUSE_OPT_VENDOR_CODE) & ((1u << 4) - 1); + const u32 fab = reg::Read(chip.FUSE_OPT_FAB_CODE) & ((1u << 6) - 1); + const u32 lot0 = reg::Read(chip.FUSE_OPT_LOT_CODE_0) /* all 32 bits */ ; + const u32 lot1 = reg::Read(chip.FUSE_OPT_LOT_CODE_1) & ((1u << 28) - 1); + const u32 wafer = reg::Read(chip.FUSE_OPT_WAFER_ID) & ((1u << 6) - 1); + const u32 x_coord = reg::Read(chip.FUSE_OPT_X_COORDINATE) & ((1u << 9) - 1); + const u32 y_coord = reg::Read(chip.FUSE_OPT_Y_COORDINATE) & ((1u << 9) - 1); + const u32 reserved = reg::Read(chip.FUSE_OPT_OPS_RESERVED) & ((1u << 6) - 1); + + /* Clear the output. */ + util::ClearMemory(out, sizeof(*out)); + + /* Copy the component bits. */ + out->ecid[0] = static_cast((lot1 << 30) | (wafer << 24) | (x_coord << 15) | (y_coord << 6) | (reserved)); + out->ecid[1] = static_cast((lot0 << 26) | (lot1 >> 2)); + out->ecid[2] = static_cast((fab << 26) | (lot0 >> 6)); + out->ecid[3] = static_cast(vendor); + } + + u64 GetDeviceId() { + /* Get the registers. */ + const volatile auto &chip = GetChipRegisters(); + + /* Read the device id components. */ + /* NOTE: Device ID is "basically" just an alternate encoding of Ecid. */ + /* It elides lot1 (and compresses lot0), but this is fine because */ + /* lot1 is fixed-value for all fused devices. */ + const u64 fab = reg::Read(chip.FUSE_OPT_FAB_CODE) & ((1u << 6) - 1); + const u32 lot0 = reg::Read(chip.FUSE_OPT_LOT_CODE_0) /* all 32 bits */ ; + const u64 wafer = reg::Read(chip.FUSE_OPT_WAFER_ID) & ((1u << 6) - 1); + const u64 x_coord = reg::Read(chip.FUSE_OPT_X_COORDINATE) & ((1u << 9) - 1); + const u64 y_coord = reg::Read(chip.FUSE_OPT_Y_COORDINATE) & ((1u << 9) - 1); + + /* Compress lot0 down from 32-bits to 26. */ + const u64 clot0 = CompressLotCode(lot0) & ((1u << 26) - 1); + + return (y_coord << 0) | + (x_coord << 9) | + (wafer << 18) | + (clot0 << 24) | + (fab << 50); + } + + DramId GetDramId() { + return static_cast(util::BitPack32{GetOdmWord(4)}.Get()); + } + HardwareType GetHardwareType() { /* Read the odm word. */ const util::BitPack32 odm_word4 = { GetOdmWord(4) }; @@ -113,33 +281,85 @@ namespace ams::fuse { } } + QuestState GetQuestState() { + return static_cast(util::BitPack32{GetOdmWord(4)}.Get()); + } + pmic::Regulator GetRegulator() { - /* TODO: How should mariko be handled? This reads from ODM word 28 in fuses (not presesnt in erista...). */ + /* TODO: How should mariko be handled? This reads from ODM word 28 in fuses (not present in erista...). */ return pmic::Regulator_Erista_Max77621; } - void GetEcid(br::BootEcid *out) { - /* Get the registers. */ - const volatile auto &chip = GetChipRegisters(); + int GetDeviceUniqueKeyGeneration() { + if (IsNewFuseFormat()) { + return util::BitPack32{GetOdmWord(2)}.Get(); + } else { + return 0; + } + } - /* Read the ecid components. */ - const u32 vendor = reg::Read(chip.FUSE_OPT_VENDOR_CODE); - const u32 fab = reg::Read(chip.FUSE_OPT_FAB_CODE); - const u32 lot0 = reg::Read(chip.FUSE_OPT_LOT_CODE_0); - const u32 lot1 = reg::Read(chip.FUSE_OPT_LOT_CODE_1); - const u32 wafer = reg::Read(chip.FUSE_OPT_WAFER_ID); - const u32 x_coord = reg::Read(chip.FUSE_OPT_X_COORDINATE); - const u32 y_coord = reg::Read(chip.FUSE_OPT_Y_COORDINATE); - const u32 reserved = reg::Read(chip.FUSE_OPT_OPS_RESERVED); + SocType GetSocType() { + switch (GetHardwareType()) { + case HardwareType_Icosa: + case HardwareType_Copper: + return SocType_Erista; + case HardwareType_Iowa: + case HardwareType_Hoag: + case HardwareType_Calcio: + case HardwareType_Five: + return SocType_Mariko; + default: + return SocType_Undefined; + } + } - /* Clear the output. */ - util::ClearMemory(out, sizeof(*out)); + int GetExpectedFuseVersion(TargetFirmware target_fw) { + return GetExpectedFuseVersionImpl(target_fw); + } - /* Copy the component bits. */ - out->ecid[0] = static_cast((lot1 << 30) | (wafer << 24) | (x_coord << 15) | (y_coord << 6) | (reserved)); - out->ecid[1] = static_cast((lot0 << 26) | (lot1 >> 2)); - out->ecid[2] = static_cast((fab << 26) | (lot0 >> 6)); - out->ecid[3] = static_cast(vendor); + bool HasRcmVulnerabilityPatch() { + /* Only check for RCM bug patch once, and cache our result. */ + if (!g_checked_for_rcm_bug_patch) { + do { + /* Mariko units are necessarily patched. */ + if (fuse::GetSocType() != SocType_Erista) { + g_has_rcm_bug_patch = true; + break; + } + + /* Some patched units use XUSB in RCM. */ + if (reg::Read(GetChipRegisters().FUSE_RESERVED_SW) & 0x80) { + g_has_rcm_bug_patch = true; + break; + } + + /* Other units have a proper ipatch instead. */ + u32 word_count = reg::Read(GetChipRegisters().FUSE_FIRST_BOOTROM_PATCH_SIZE) & 0x7F; + u32 word_addr = 191; + + while (word_count && !g_has_rcm_bug_patch) { + u32 word0 = ReadWord(word_addr); + u32 ipatch_count = (word0 >> 16) & 0xF; + + for (u32 i = 0; i < ipatch_count && !g_has_rcm_bug_patch; ++i) { + u32 word = ReadWord(word_addr - (i + 1)); + u32 addr = (word >> 16) * 2; + + if (addr == 0x769a) { + g_has_rcm_bug_patch = true; + break; + } + } + + word_addr -= word_count; + word_count = word0 >> 25; + } + } while (0); + + g_checked_for_rcm_bug_patch = true; + } + + return g_has_rcm_bug_patch; } } diff --git a/libraries/libexosphere/source/fuse/fuse_registers.hpp b/libraries/libexosphere/source/fuse/fuse_registers.hpp index 012ef2a87..ff0fcc571 100644 --- a/libraries/libexosphere/source/fuse/fuse_registers.hpp +++ b/libraries/libexosphere/source/fuse/fuse_registers.hpp @@ -213,6 +213,31 @@ namespace ams::fuse { #define DEFINE_FUSE_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(FUSE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) #define DEFINE_FUSE_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (FUSE, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + DEFINE_FUSE_REG_TWO_BIT_ENUM(FUSECTRL_CMD, 0, IDLE, READ, WRITE, SENSE_CTRL); + + DEFINE_FUSE_REG(FUSECTRL_STATE, 16, 5); + + enum FUSE_FUSECTRL_STATE { + FUSE_FUSECTRL_STATE_RESET = 0, + FUSE_FUSECTRL_STATE_POST_RESET = 1, + FUSE_FUSECTRL_STATE_LOAD_ROW0 = 2, + FUSE_FUSECTRL_STATE_LOAD_ROW1 = 3, + FUSE_FUSECTRL_STATE_IDLE = 4, + FUSE_FUSECTRL_STATE_READ_SETUP = 5, + FUSE_FUSECTRL_STATE_READ_STROBE = 6, + FUSE_FUSECTRL_STATE_SAMPLE_FUSES = 7, + FUSE_FUSECTRL_STATE_READ_HOLD = 8, + FUSE_FUSECTRL_STATE_FUSE_SRC_SETUP = 9, + FUSE_FUSECTRL_STATE_WRITE_SETUP = 10, + FUSE_FUSECTRL_STATE_WRITE_ADDR_SETUP = 11, + FUSE_FUSECTRL_STATE_WRITE_PROGRAM = 12, + FUSE_FUSECTRL_STATE_WRITE_ADDR_HOLD = 13, + FUSE_FUSECTRL_STATE_FUSE_SRC_HOLD = 14, + FUSE_FUSECTRL_STATE_LOAD_RIR = 15, + FUSE_FUSECTRL_STATE_READ_BEFORE_WRITE_SETUP = 16, + FUSE_FUSECTRL_STATE_READ_DEASSERT_PD = 17, + }; + DEFINE_FUSE_REG_BIT_ENUM(PRIVATEKEYDISABLE_TZ_STICKY_BIT_VAL, 4, KEY_VISIBLE, KEY_INVISIBLE); DEFINE_FUSE_REG_BIT_ENUM(PRIVATEKEYDISABLE_PRIVATEKEYDISABLE_VAL_KEY, 0, VISIBLE, INVISIBLE);