exo2: implement SmcGetConfig

This commit is contained in:
Michael Scire 2020-05-15 02:32:17 -07:00 committed by SciresM
parent e3eadcd2e3
commit 6bf283ec2e
15 changed files with 640 additions and 45 deletions

View file

@ -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<u32>(temp_reg >> 32);
__asm__ __volatile__("mrs %0, elr_el3" : "=r"(temp_reg) :: "memory");
*(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x18) = static_cast<u32>(temp_reg >> 0);
*(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x1C) = static_cast<u32>(temp_reg >> 32);
*(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x10) = static_cast<u32>(temp_reg >> 0);
*(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x14) = static_cast<u32>(temp_reg >> 32);
__asm__ __volatile__("mrs %0, far_el3" : "=r"(temp_reg) :: "memory");
*(volatile u32 *)(secmon::MemoryRegionVirtualDebug.GetAddress() + 0x18) = static_cast<u32>(temp_reg >> 0);

View file

@ -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]!

View file

@ -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<u32>(g_deprecated_boot_reason_state) << 24) | (g_deprecated_boot_reason_value & 0x00FFFFFF);
}
}

View file

@ -29,4 +29,6 @@ namespace ams::secmon {
void GetPackage2Hash(se::Sha256Hash *out);
void SetPackage2Hash(const se::Sha256Hash &hash);
u32 GetDeprecatedBootReason();
}

View file

@ -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<u64>(SmcResult::Success)) {
*(volatile u32 *)(MemoryRegionVirtualDebug.GetAddress()) = 0xCCCCCCCC;
*(volatile u32 *)(MemoryRegionVirtualDebug.GetAddress() + 0x10) = static_cast<u32>(info.function_id);
for (size_t i = 0; i < sizeof(args) / sizeof(u32); ++i) {
((volatile u32 *)(MemoryRegionVirtualDebug.GetAddress() + 0x20))[i] = reinterpret_cast<u32 *>(std::addressof(args))[i];
}
*(volatile u32 *)(MemoryRegionVirtualDevicePmc.GetAddress() + 0x50) = 0x02;
*(volatile u32 *)(MemoryRegionVirtualDevicePmc.GetAddress() + 0x00) = 0x10;
util::WaitMicroSeconds(1000);
}
#endif

View file

@ -15,18 +15,272 @@
*/
#include <exosphere.hpp>
#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<DebugFillMemory::Next, 1, bool>;
using EnableUserPmuAccess = util::BitPack32::Field<EnableUserExceptionHandlers::Next, 1, bool>;
using IncreaseThreadResourceLimit = util::BitPack32::Field<EnableUserPmuAccess::Next, 1, bool>;
using Reserved4 = util::BitPack32::Field<IncreaseThreadResourceLimit::Next, 4, u32>;
using UseSecureMonitorPanicCall = util::BitPack32::Field<Reserved4::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_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<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;
}
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] = 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<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()) << 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) {

View file

@ -34,7 +34,7 @@ namespace ams::secmon::smc {
IsDevelopmentFunctionEnabled = 11,
KernelConfiguration = 12,
IsChargerHiZModeEnabled = 13,
IsQuest = 14,
QuestState = 14,
RegulatorType = 15,
DeviceUniqueKeyGeneration = 16,
Package2Hash = 17,

View file

@ -24,9 +24,7 @@ namespace ams::secmon::smc {
template<size_t N>
constexpr void SetRegisterTableAllowed(std::array<u8, N> &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);
}

View file

@ -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.

View file

@ -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();
}

View file

@ -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];

View file

@ -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<EmummcBaseConfiguration>::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<EmummcConfiguration>::value);
static_assert(sizeof(EmummcConfiguration) <= 0x200);

View file

@ -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<SecureMonitorConfiguration>::value);
static_assert(sizeof(SecureMonitorConfiguration) == 0x80);

View file

@ -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<HardwareState1::Next, 1, int>;
@ -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<volatile FuseRegisterRegion *>(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<OdmWord4::FormatVersion>() == 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<TargetFirmware>(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<u32>((lot1 << 30) | (wafer << 24) | (x_coord << 15) | (y_coord << 6) | (reserved));
out->ecid[1] = static_cast<u32>((lot0 << 26) | (lot1 >> 2));
out->ecid[2] = static_cast<u32>((fab << 26) | (lot0 >> 6));
out->ecid[3] = static_cast<u32>(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<DramId>(util::BitPack32{GetOdmWord(4)}.Get<OdmWord4::DramId>());
}
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<QuestState>(util::BitPack32{GetOdmWord(4)}.Get<OdmWord4::QuestState>());
}
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<OdmWord2::DeviceUniqueKeyGeneration>();
} 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<u32>((lot1 << 30) | (wafer << 24) | (x_coord << 15) | (y_coord << 6) | (reserved));
out->ecid[1] = static_cast<u32>((lot0 << 26) | (lot1 >> 2));
out->ecid[2] = static_cast<u32>((fab << 26) | (lot0 >> 6));
out->ecid[3] = static_cast<u32>(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;
}
}

View file

@ -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);