/* * 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 . */ #include #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; using EnableUserPmuAccess = util::BitPack32::Field; using IncreaseThreadResourceLimit = util::BitPack32::Field; using DisableDynamicResourceLimits = util::BitPack32::Field; using Reserved5 = 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_IowaHynix1y4GB] = pkg1::MemorySize_4GB, [fuse::DramId_IcosaSamsung6GB] = pkg1::MemorySize_6GB, [fuse::DramId_HoagHynix1y4GB] = pkg1::MemorySize_4GB, [fuse::DramId_AulaHynix1y4GB] = 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_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, }; 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; } constinit u64 g_payload_address = 0; constinit bool g_set_true_target_firmware = false; 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::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(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()) << 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(static_cast(secmon::GetLogPort())) << 32) | static_cast(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(ATMOSPHERE_SUPPORTED_HOS_VERSION_MAJOR & 0xFF) << 24) | (static_cast(ATMOSPHERE_SUPPORTED_HOS_VERSION_MINOR & 0xFF) << 16) | (static_cast(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(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()) << 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(args.r[1])) { case ConfigItem::IsChargerHiZModeEnabled: /* Configure the HiZ mode. */ SetChargerHiZModeEnabled(static_cast(args.r[3])); break; case ConfigItem::ExosphereApiVersion: if (!g_set_true_target_firmware) { ::ams::secmon::impl::SetTargetFirmware(static_cast(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(args.r[3])) { case UserRebootType_None: break; case UserRebootType_ToRcm: PerformUserRebootToRcm(); break; case UserRebootType_ToPayload: PerformUserRebootToPayload(); break; case UserRebootType_ToFatalError: PerformUserRebootToFatalError(); break; default: return SmcResult::InvalidArgument; } } else /* if (soc_type == fuse::SocType_Mariko) */ { switch (static_cast(args.r[3])) { case UserRebootType_ToFatalError: PerformUserRebootToFatalError(); break; default: return SmcResult::InvalidArgument; } } break; case ConfigItem::ExosphereNeedsShutdown: if (soc_type == fuse::SocType_Erista) { if (args.r[3] != 0) { PerformUserShutDown(); } } else /* if (soc_type == fuse::SocType_Mariko) */ { return SmcResult::NotSupported; } 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(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(static_cast(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 auto dram_id = fuse::GetDramId(); AMS_ABORT_UNLESS(dram_id < fuse::DramId_Count); return DramIdToMemorySize[dram_id]; } }