/*
 * 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_page_mapper.hpp"
#include "secmon_user_power_management.hpp"
#include "rebootstub_bin.h"
namespace ams::secmon {
    namespace {
        constexpr inline const uintptr_t PMC = MemoryRegionVirtualDevicePmc.GetAddress();
        constexpr inline const u32 RebootStubPhysicalAddress = MemoryRegionPhysicalIramRebootStub.GetAddress();
        enum RebootStubAction {
            RebootStubAction_ShutDown      = 0,
            RebootStubAction_JumpToPayload = 1,
        };
        NORETURN void PerformPmcReboot() {
            /* Write MAIN_RST. */
            reg::Write(PMC + APBDEV_PMC_CNTRL, 0x10);
            while (true) {
                /* ... */
            }
        }
        void LoadRebootStub(u32 action) {
            /* Configure the bootrom to boot to warmboot payload. */
            reg::Write(PMC + APBDEV_PMC_SCRATCH0, 0x1);
            /* Patch the bootrom to perform an SVC immediately after the second spare write. */
            reg::Write(PMC + APBDEV_PMC_SCRATCH45, 0x2E38DFFF);
            reg::Write(PMC + APBDEV_PMC_SCRATCH46, 0x6001DC28);
            /* Patch the bootrom to jump to the reboot stub we'll prepare in iram on SVC. */
            reg::Write(PMC + APBDEV_PMC_SCRATCH33, RebootStubPhysicalAddress);
            reg::Write(PMC + APBDEV_PMC_SCRATCH40, 0x6000F208);
            {
                /* Map the iram page. */
                AtmosphereIramPageMapper mapper(RebootStubPhysicalAddress);
                AMS_ABORT_UNLESS(mapper.Map());
                /* Copy the reboot stub. */
                AMS_ABORT_UNLESS(mapper.CopyToMapping(RebootStubPhysicalAddress, rebootstub_bin, rebootstub_bin_size));
                /* Set the reboot type. */
                AMS_ABORT_UNLESS(mapper.CopyToMapping(RebootStubPhysicalAddress + 4, std::addressof(action), sizeof(action)));
            }
        }
    }
    void PerformUserRebootToRcm() {
        /* Configure the bootrom to boot to rcm. */
        reg::Write(PMC + APBDEV_PMC_SCRATCH0, 0x2);
        /* Reboot. */
        PerformPmcReboot();
    }
    void PerformUserRebootToPayload() {
        /* Load our reboot stub to iram. */
        LoadRebootStub(RebootStubAction_JumpToPayload);
        /* Reboot. */
        PerformPmcReboot();
    }
    void PerformUserShutDown() {
        /* Load our reboot stub to iram. */
        LoadRebootStub(RebootStubAction_ShutDown);
        /* Reboot. */
        PerformPmcReboot();
    }
}