/*
 * 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 <http://www.gnu.org/licenses/>.
 */
#include <stratosphere.hpp>
#include "boot_power_utils.hpp"
#include "boot_pmic_driver.hpp"
#include "fusee_bin.h"

namespace ams::boot {

    namespace {

        /* Convenience definitions. */
        constexpr uintptr_t IramBase = 0x40000000ull;
        constexpr uintptr_t IramPayloadBase = 0x40010000ull;
        constexpr size_t IramSize = 0x40000;
        constexpr size_t IramPayloadMaxSize = 0x24000;
        constexpr size_t IramFatalErrorContextOffset = 0x2E000;

        /* Globals. */
        alignas(os::MemoryPageSize) u8 g_work_page[os::MemoryPageSize];

        /* Helpers. */
        void ClearIram() {
            /* Make page FFs. */
            std::memset(g_work_page, 0xFF, sizeof(g_work_page));

            /* Overwrite all of IRAM with FFs. */
            for (size_t ofs = 0; ofs < IramSize; ofs += sizeof(g_work_page)) {
                exosphere::CopyToIram(IramBase + ofs, g_work_page, sizeof(g_work_page));
            }
        }

        void DoRebootToPayload() {
            /* Ensure clean IRAM state. */
            ClearIram();

            /* Copy in payload. */
            for (size_t ofs = 0; ofs < fusee_bin_size; ofs += sizeof(g_work_page)) {
                std::memcpy(g_work_page, fusee_bin + ofs, std::min(static_cast<size_t>(fusee_bin_size - ofs), sizeof(g_work_page)));
                exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page));
            }

            exosphere::ForceRebootToIramPayload();
        }

        void DoRebootToFatalError(const ams::FatalErrorContext *ctx) {
            /* Ensure clean IRAM state. */
            ClearIram();

            /* Copy in payload. */
            for (size_t ofs = 0; ofs < fusee_bin_size; ofs += sizeof(g_work_page)) {
                std::memcpy(g_work_page, fusee_bin + ofs, std::min(static_cast<size_t>(fusee_bin_size - ofs), sizeof(g_work_page)));
                exosphere::CopyToIram(IramPayloadBase + ofs, g_work_page, sizeof(g_work_page));
            }


            /* Copy in fatal error context, if relevant. */
            if (ctx != nullptr) {
                std::memset(g_work_page, 0xCC, sizeof(g_work_page));
                std::memcpy(g_work_page, ctx, sizeof(*ctx));
                exosphere::CopyToIram(IramPayloadBase + IramFatalErrorContextOffset, g_work_page, sizeof(g_work_page));
            }

            exosphere::ForceRebootToFatalError();
        }

    }

    void RebootSystem() {
        if (spl::GetSocType() == spl::SocType_Erista) {
            DoRebootToPayload();
        } else {
            /* On Mariko, we can't reboot to payload, so we should just do a reboot. */
            PmicDriver().RebootSystem();
        }
    }

    void ShutdownSystem() {
        PmicDriver().ShutdownSystem();
    }

    void SetInitialRebootPayload() {
        ::ams::SetInitialRebootPayload(fusee_bin, fusee_bin_size);
    }

    void RebootForFatalError(ams::FatalErrorContext *ctx) {
        DoRebootToFatalError(ctx);
    }

}
