// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "core/core.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_system_resource.h"

namespace Kernel {

Result KSecureSystemResource::Initialize(size_t size, KResourceLimit* resource_limit,
                                         KMemoryManager::Pool pool) {
    // Set members.
    m_resource_limit = resource_limit;
    m_resource_size = size;
    m_resource_pool = pool;

    // Determine required size for our secure resource.
    const size_t secure_size = this->CalculateRequiredSecureMemorySize();

    // Reserve memory for our secure resource.
    KScopedResourceReservation memory_reservation(
        m_resource_limit, Svc::LimitableResource::PhysicalMemoryMax, secure_size);
    R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);

    // Allocate secure memory.
    R_TRY(KSystemControl::AllocateSecureMemory(m_kernel, std::addressof(m_resource_address),
                                               m_resource_size, static_cast<u32>(m_resource_pool)));
    ASSERT(m_resource_address != 0);

    // Ensure we clean up the secure memory, if we fail past this point.
    ON_RESULT_FAILURE {
        KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
                                         static_cast<u32>(m_resource_pool));
    };

    // Check that our allocation is bigger than the reference counts needed for it.
    const size_t rc_size =
        Common::AlignUp(KPageTableSlabHeap::CalculateReferenceCountSize(m_resource_size), PageSize);
    R_UNLESS(m_resource_size > rc_size, ResultOutOfMemory);

    // Get resource pointer.
    KPhysicalAddress resource_paddr =
        KPageTable::GetHeapPhysicalAddress(m_kernel, m_resource_address);
    auto* resource =
        m_kernel.System().DeviceMemory().GetPointer<KPageTableManager::RefCount>(resource_paddr);

    // Initialize slab heaps.
    m_dynamic_page_manager.Initialize(m_resource_address + rc_size, m_resource_size - rc_size,
                                      PageSize);
    m_page_table_heap.Initialize(std::addressof(m_dynamic_page_manager), 0, resource);
    m_memory_block_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);
    m_block_info_heap.Initialize(std::addressof(m_dynamic_page_manager), 0);

    // Initialize managers.
    m_page_table_manager.Initialize(std::addressof(m_dynamic_page_manager),
                                    std::addressof(m_page_table_heap));
    m_memory_block_slab_manager.Initialize(std::addressof(m_dynamic_page_manager),
                                           std::addressof(m_memory_block_heap));
    m_block_info_manager.Initialize(std::addressof(m_dynamic_page_manager),
                                    std::addressof(m_block_info_heap));

    // Set our managers.
    this->SetManagers(m_memory_block_slab_manager, m_block_info_manager, m_page_table_manager);

    // Commit the memory reservation.
    memory_reservation.Commit();

    // Open reference to our resource limit.
    m_resource_limit->Open();

    // Set ourselves as initialized.
    m_is_initialized = true;

    R_SUCCEED();
}

void KSecureSystemResource::Finalize() {
    // Check that we have no outstanding allocations.
    ASSERT(m_memory_block_slab_manager.GetUsed() == 0);
    ASSERT(m_block_info_manager.GetUsed() == 0);
    ASSERT(m_page_table_manager.GetUsed() == 0);

    // Free our secure memory.
    KSystemControl::FreeSecureMemory(m_kernel, m_resource_address, m_resource_size,
                                     static_cast<u32>(m_resource_pool));

    // Release the memory reservation.
    m_resource_limit->Release(Svc::LimitableResource::PhysicalMemoryMax,
                              this->CalculateRequiredSecureMemorySize());

    // Close reference to our resource limit.
    m_resource_limit->Close();
}

size_t KSecureSystemResource::CalculateRequiredSecureMemorySize(size_t size,
                                                                KMemoryManager::Pool pool) {
    return KSystemControl::CalculateRequiredSecureMemorySize(size, static_cast<u32>(pool));
}

} // namespace Kernel
