From 94a8be89ace8468cfca2a227a82d06f7b20d8c65 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 15 May 2020 02:32:17 -0700 Subject: [PATCH] exo2: implement SmcGetConfig --- 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 ++++++++++++++++-- libexosphere/source/fuse/fuse_registers.hpp | 25 ++ 7 files changed, 346 insertions(+), 23 deletions(-) diff --git a/config/common.mk b/config/common.mk index e3a12dd7..ebe9bcfb 100644 --- a/config/common.mk +++ b/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/libexosphere/include/exosphere/fuse.hpp b/libexosphere/include/exosphere/fuse.hpp index 55efdb33..e6ff9ebb 100644 --- a/libexosphere/include/exosphere/fuse.hpp +++ b/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/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp b/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp index 93f70df1..86eda527 100644 --- a/libexosphere/include/exosphere/pkg1/pkg1_bootloader_parameters.hpp +++ b/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/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp b/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp index 044420e8..b9c8c47d 100644 --- a/libexosphere/include/exosphere/secmon/secmon_emummc_context.hpp +++ b/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/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp b/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp index 1aa0b679..2b3822b0 100644 --- a/libexosphere/include/exosphere/secmon/secmon_monitor_context.hpp +++ b/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/libexosphere/source/fuse/fuse_api.cpp b/libexosphere/source/fuse/fuse_api.cpp index 32d49139..c01286bf 100644 --- a/libexosphere/source/fuse/fuse_api.cpp +++ b/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/libexosphere/source/fuse/fuse_registers.hpp b/libexosphere/source/fuse/fuse_registers.hpp index 012ef2a8..ff0fcc57 100644 --- a/libexosphere/source/fuse/fuse_registers.hpp +++ b/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);