/*
 * 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 "fatal_device_page_table.hpp"

namespace ams::secmon::fatal {

    namespace {

        constexpr inline auto Port = sdmmc::Port_SdCard0;

        ALWAYS_INLINE u8 *GetSdCardWorkBuffer() {
            return MemoryRegionVirtualDramSdmmcMappedData.GetPointer<u8>() + MemoryRegionVirtualDramSdmmcMappedData.GetSize() - mmu::PageSize;
        }

        ALWAYS_INLINE u8 *GetSdCardDmaBuffer() {
            return MemoryRegionVirtualDramSdmmcMappedData.GetPointer<u8>();
        }

        constexpr inline size_t SdCardDmaBufferSize = MemoryRegionVirtualDramSdmmcMappedData.GetSize() - mmu::PageSize;
        constexpr inline size_t SdCardDmaBufferSectors = SdCardDmaBufferSize / sdmmc::SectorSize;
        static_assert(util::IsAligned(SdCardDmaBufferSize, sdmmc::SectorSize));

    }

    Result InitializeSdCard() {
        /* Map main memory for the sdmmc device. */
        InitializeDevicePageTableForSdmmc1();

        /* Initialize sdmmc library. */
        sdmmc::Initialize(Port);

        sdmmc::SetSdCardWorkBuffer(Port, GetSdCardWorkBuffer(), sdmmc::SdCardWorkBufferSize);

        //sdmmc::Deactivate(Port);
        R_TRY(sdmmc::Activate(Port));

        return ResultSuccess();
    }

    Result CheckSdCardConnection(sdmmc::SpeedMode *out_sm, sdmmc::BusWidth *out_bw) {
        return sdmmc::CheckSdCardConnection(out_sm, out_bw, Port);
    }

    Result ReadSdCard(void *dst, size_t size, size_t sector_index, size_t sector_count) {
        /* Validate that our buffer is valid. */
        AMS_ASSERT(size >= sector_count * sdmmc::SectorSize);

        /* Repeatedly read sectors. */
        u8 *dst_u8 = static_cast<u8 *>(dst);
        void * const dma_buffer = GetSdCardDmaBuffer();
        while (sector_count > 0) {
            /* Read sectors into the DMA buffer. */
            const size_t cur_sectors = std::min(sector_count, SdCardDmaBufferSectors);
            const size_t cur_size    = cur_sectors * sdmmc::SectorSize;
            R_TRY(sdmmc::Read(dma_buffer, cur_size, Port, sector_index, cur_sectors));

            /* Copy data from the DMA buffer to the output. */
            std::memcpy(dst_u8, dma_buffer, cur_size);

            /* Advance. */
            dst_u8       += cur_size;
            sector_index += cur_sectors;
            sector_count -= cur_sectors;
        }

        return ResultSuccess();
    }

    Result WriteSdCard(size_t sector_index, size_t sector_count, const void *src, size_t size) {
        /* Validate that our buffer is valid. */
        AMS_ASSERT(size >= sector_count * sdmmc::SectorSize);

        /* Repeatedly read sectors. */
        const u8 *src_u8 = static_cast<const u8 *>(src);
        void * const dma_buffer = GetSdCardDmaBuffer();
        while (sector_count > 0) {
            /* Copy sectors into the DMA buffer. */
            const size_t cur_sectors = std::min(sector_count, SdCardDmaBufferSectors);
            const size_t cur_size    = cur_sectors * sdmmc::SectorSize;
            std::memcpy(dma_buffer, src_u8, cur_size);

            /* Write sectors to the sd card. */
            R_TRY(sdmmc::Write(Port, sector_index, cur_sectors, dma_buffer, cur_size));

            /* Advance. */
            src_u8       += cur_size;
            sector_index += cur_sectors;
            sector_count -= cur_sectors;
        }

        return ResultSuccess();
    }

}