Newer
Older
Atmosphere / exosphere2 / program / source / smc / secmon_smc_info.cpp
@Michael Scire Michael Scire on 15 Jun 2020 13 KB exo2: implement SmcGetConfig
/*
 * 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 <http://www.gnu.org/licenses/>.
 */
#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) {
        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;
    }

}