/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/mm/kpool.cc
 * DESCRIPTION:     Kernel pool memory management
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 *                  Aiken Harris <harraiken91@gmail.com>
 */

#include <xtos.hh>


/**
 * Allocates a new kernel stack.
 *
 * @param Stack
 *        Supplies a pointer to the memory area that will contain a new kernel stack.
 *
 * @param LargeStack
 *        Determines whether the stack is large or small.
 *
 * @param SystemNode
 *        Specifies a preferred node used for new stack on multi-processor systems.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTAPI
XTSTATUS
MM::KernelPool::AllocateKernelStack(OUT PVOID *Stack,
                                    IN ULONG StackSize)
{
    PFN_COUNT StackPages;
    PMMPTE PointerPte, StackPte;
    MMPTE TempPte, InvalidPte;
    PFN_NUMBER PageFrameIndex;
    ULONG Index;

    /* Initialize the output stack pointer to NULLPTR */
    *Stack = NULLPTR;

    /* Convert the requested stack size into a page count */
    StackPages = SIZE_TO_PAGES(StackSize);

    /* Reserve PTEs for the stack pages, plus a guard page */
    StackPte = MM::Pte::ReserveSystemPtes(StackPages + 1, SystemPteSpace);
    if(!StackPte)
    {
        /* Failed to reserve PTEs for the new kernel stack */
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Set up a template for an invalid PTE */
    MM::Paging::SetPte(&InvalidPte, 0, MM_PTE_GUARDED);

    /* Set up a template for a valid, writable stack PTE */
    MM::Paging::ClearPte(&TempPte);
    MM::Paging::SetPte(&TempPte, 0, MM_PTE_READWRITE | MM_PTE_CACHE_ENABLE);

    /* Acquire the PFN database lock and raise runlevel to DISPATCH_LEVEL */
    KE::RaiseRunLevel RunLevel(DISPATCH_LEVEL);
    KE::QueuedSpinLockGuard SpinLock(SystemSpaceLock);

    /* Start iterating from the base of the reserved PTE block */
    PointerPte = StackPte;

    /* Loop through each page of the stack that needs to be allocated */
    for(Index = 0; Index < StackPages; Index++)
    {
        /* Advance to the next PTE */
        PointerPte = MM::Paging::GetNextPte(PointerPte);

        /* Allocate a physical page and temporarily mark the PTE as invalid */
        PageFrameIndex = MM::Pfn::AllocatePhysicalPage(MM::Colors::GetNextColor());
        *PointerPte = InvalidPte;

        /* Associate the physical page with its corresponding PTE in the PFN database */
        MM::Pfn::LinkPfn(PageFrameIndex, PointerPte, TRUE);

        /* Make the PTE valid, mapping the virtual address to the physical page */
        MM::Paging::SetPte(&TempPte, PageFrameIndex, 0);
        *PointerPte = TempPte;
    }

    /* Zero the newly allocated stack memory, skipping the guard page */
    RTL::Memory::ZeroMemory(MM::Paging::GetPteVirtualAddress(MM::Paging::GetNextPte(StackPte)),
                            MM_PAGE_SIZE * StackPages);

    /* Return a pointer to the top of the new stack */
    *Stack = MM::Paging::GetPteVirtualAddress(MM::Paging::AdvancePte(StackPte, StackPages + 1));
    return STATUS_SUCCESS;
}

/**
 * Allocates a buffer for structures needed by a processor and assigns it to a corresponding CPU.
 *
 * @param CpuNumber
 *        Specifies the zero-indexed CPU number as an owner of the allocated structures.
 *
 * @param StructuresData
 *        Supplies a pointer to the memory area that will contain the allocated buffer.
 *
 * @return This routine returns a status code.
 *
 * @since XT 1.0
 */
XTAPI
XTSTATUS
MM::KernelPool::AllocateProcessorStructures(IN ULONG CpuNumber,
                                            OUT PVOID *StructuresData)
{
    PKPROCESSOR_BLOCK ProcessorBlock;
    PVOID ProcessorStructures;
    UINT_PTR Address;
    XTSTATUS Status;

    /* Assign memory for processor structures */
    Status = MM::Allocator::AllocatePool(NonPagedPool, KPROCESSOR_STRUCTURES_SIZE, &ProcessorStructures);
    if(Status != STATUS_SUCCESS)
    {
        /* Failed to allocate memory, return status code */
        return Status;
    }

    /* Make sure all structures are zeroed */
    RTL::Memory::ZeroMemory(ProcessorStructures, KPROCESSOR_STRUCTURES_SIZE);

    /* Align address to page size boundary and find a space for processor block */
    Address = ROUND_UP((UINT_PTR)ProcessorStructures, MM_PAGE_SIZE);
    ProcessorBlock = (PKPROCESSOR_BLOCK)((PUCHAR)Address +
                     (KERNEL_STACKS * KERNEL_STACK_SIZE) +
                     (GDT_ENTRIES * sizeof(KGDTENTRY)));

    /* Store processor number in the processor block */
    ProcessorBlock->CpuNumber = CpuNumber;

    /* Return pointer to the processor structures */
    *StructuresData = ProcessorStructures;

    /* Return success */
    return STATUS_SUCCESS;
}

/**
 * Destroys a kernel stack and frees page table entry.
 *
 * @param Stack
 *        Supplies a pointer to the memory area containing a kernel stack.
 *
 * @param LargeStack
 *        Determines whether the stack is large or small.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
MM::KernelPool::FreeKernelStack(IN PVOID Stack,
                                IN ULONG StackSize)
{
    PFN_COUNT StackPages;
    PMMPTE PointerPte;
    ULONG Index;

    /* Get the PTE for the top of the stack, including the guard page */
    PointerPte = MM::Paging::AdvancePte(MM::Paging::GetPteAddress(Stack), -1);

    /* Convert the stack size into a page count */
    StackPages = SIZE_TO_PAGES(StackSize);

    /* Start a guarded code block */
    {
        /* Acquire the PFN database lock */
        KE::QueuedSpinLockGuard SpinLock(SystemSpaceLock);

        /* Loop through each page of the stack that needs to be freed */
        for(Index = 0; Index < StackPages; Index++)
        {
            /* Ensure the PTE is valid */
            if(MM::Paging::PteValid(PointerPte))
            {
                /* Free the physical page */
                MM::Pfn::FreePhysicalPage(PointerPte);
            }

            /* Advance to the next PTE */
            PointerPte = MM::Paging::AdvancePte(PointerPte, -1);
        }
    }

    /* Release all system PTEs used by the stack, including the guard page */
    MM::Pte::ReleaseSystemPtes(PointerPte, StackPages + 1, SystemPteSpace);
}

/**
 * Destroys an unused set of processor structures.
 *
 * @param StructuresData
 *        Supplies a pointer to the memory area containing the allocated buffer.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
MM::KernelPool::FreeProcessorStructures(IN PVOID StructuresData)
{
    /* Check if the provided pointer is valid */
    if(StructuresData != NULLPTR)
    {
        /* Release the contiguous memory block back */
        MM::Allocator::FreePool(StructuresData, 0);
    }
}
