/* * Copyright (c) 2018-2020 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 . */ #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 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) { return GetConfig(args, false); } SmcResult SmcGetConfigKern(SmcArguments &args) { return GetConfig(args, true); } SmcResult SmcSetConfig(SmcArguments &args) { /* TODO */ return SmcResult::NotImplemented; } /* This is an atmosphere extension smc. */ SmcResult SmcGetEmummcConfig(SmcArguments &args) { /* TODO */ return SmcResult::NotImplemented; } /* For exosphere's usage. */ pkg1::MemorySize GetPhysicalMemorySize() { const auto dram_id = fuse::GetDramId(); AMS_ABORT_UNLESS(dram_id < fuse::DramId_Count); return DramIdToMemorySize[dram_id]; } }