/**
 * PROJECT:         ExectOS
 * COPYRIGHT:       See COPYING.md in the top level directory
 * FILE:            xtoskrnl/ke/sysres.cc
 * DESCRIPTION:     System resources management; This code is based on the MinocaOS implementation
 * DEVELOPERS:      Rafal Kupiec <belliash@codingworkshop.eu.org>
 */

#include <xtos.hh>


/**
 * Looks for an unacquired system resource of the specified type and acquires it.
 *
 * @param ResourceType
 *        Supplies system resource type.
 *
 * @param ResourceHeader
 *        Specifies a memory area where a pointer to the system resource header will be stored.
 *
 * @return This routine returns a status code indicating the success or failure of the operation.
 *
 * @since XT 1.0
 */
XTAPI
XTSTATUS
KE::SystemResources::AcquireResource(IN SYSTEM_RESOURCE_TYPE ResourceType,
                                     OUT PSYSTEM_RESOURCE_HEADER *ResourceHeader)
{
    /* Get system resource and acquire an ownership */
    return GetSystemResource(ResourceType, TRUE, ResourceHeader);
}

/**
 * Looks for an unacquired system resource of the specified type.
 *
 * @param ResourceType
 *        Supplies system resource type.
 *
 * @param Acquire
 *        Specifies whether system resource should be acquired or not.
 *
 * @param ResourceHeader
 *        Specifies a memory area where a pointer to the system resource header will be stored.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
XTSTATUS
KE::SystemResources::GetSystemResource(IN SYSTEM_RESOURCE_TYPE ResourceType,
                                       IN BOOLEAN ResourceLock,
                                       OUT PSYSTEM_RESOURCE_HEADER *ResourceHeader)
{
    PSYSTEM_RESOURCE_HEADER Resource;
    PLIST_ENTRY ListEntry;
    BOOLEAN Interrupts;
    XTSTATUS Status;

    /* Assume resource found successfully */
    Status = STATUS_SUCCESS;

    /* Check if interrupts are enabled */
    Interrupts = AR::CpuFunctions::InterruptsEnabled();

    /* Disable interrupts and acquire a spinlock */
    AR::CpuFunctions::ClearInterruptFlag();
    KE::SpinLock::AcquireSpinLock(&ResourcesLock);

    /* Iterate through system resources list */
    ListEntry = ResourcesListHead.Flink;
    while(ListEntry != &ResourcesListHead)
    {
        /* Get resource header */
        Resource = CONTAIN_RECORD(ListEntry, SYSTEM_RESOURCE_HEADER, ListEntry);

        /* Check if resource type matches */
        if(Resource->ResourceType == ResourceType)
        {
            /* Check if resource is locked */
            if(Resource->ResourceLocked)
            {
                /* Resource locked, set status code and stop browsing a list */
                Status = STATUS_RESOURCE_LOCKED;
                break;
            }

            /* Check if resource lock should be acquired */
            if(ResourceLock)
            {
                /* Acquire resource lock */
                Resource->ResourceLocked = TRUE;
            }

            /* Stop browsing a list */
            break;
        }

        /* Go to the next list entry */
        ListEntry = ListEntry->Flink;
    }

    /* Check if resource was found */
    if(ListEntry == &ResourcesListHead)
    {
        /* Resource not found, return NULLPTR */
        Resource = NULLPTR;
        Status = STATUS_NOT_FOUND;
    }

    /* Release spinlock and re-enable interrupts if necessary */
    KE::SpinLock::ReleaseSpinLock(&ResourcesLock);
    if(Interrupts)
    {
        /* Re-enable interrupts */
        AR::CpuFunctions::SetInterruptFlag();
    }

    /* Return resource header and status code */
    *ResourceHeader = Resource;
    return Status;
}

/**
 * Looks for an unacquired system resource of the specified type and returns it without acquiring an ownership.
 *
 * @param ResourceType
 *        Supplies system resource type.
 *
 * @param ResourceHeader
 *        Specifies a memory area where a pointer to the system resource header will be stored.
 *
 * @return This routine returns a status code indicating the success or failure of the operation.
 *
 * @since XT 1.0
 */
XTAPI
XTSTATUS
KE::SystemResources::GetResource(IN SYSTEM_RESOURCE_TYPE ResourceType,
                                 OUT PSYSTEM_RESOURCE_HEADER *ResourceHeader)
{
    /* Get system resource without acquiring an ownership */
    return GetSystemResource(ResourceType, FALSE, ResourceHeader);
}

/**
 * Initializes system resource management.
 *
 * @return This routine returns a status code indicating the success or failure of the operation.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::SystemResources::InitializeResources(VOID)
{
    PSYSTEM_RESOURCE_HEADER ResourceHeader;
    PLIST_ENTRY ListEntry, NextListEntry;
    ULONG ResourceSize;

    /* Initialize system resources spin lock and resource list */
    KE::SpinLock::InitializeSpinLock(&ResourcesLock);
    RTL::LinkedList::InitializeListHead(&ResourcesListHead);

    /* Make sure there are some system resources available */
    if(!RTL::LinkedList::ListEmpty(KE::BootInformation::GetSystemResources()))
    {
        /* Iterate through system resources list */
        ListEntry = KE::BootInformation::GetSystemResources()->Flink;
        while(ListEntry != KE::BootInformation::GetSystemResources())
        {
            /* Get resource header and next list entry */
            ResourceHeader = CONTAIN_RECORD(ListEntry, SYSTEM_RESOURCE_HEADER, ListEntry);
            NextListEntry = ListEntry->Flink;

            /* Basic resource type validation */
            switch(ResourceHeader->ResourceType)
            {
                case SystemResourceAcpi:
                    /* ACPI system resource */
                    ResourceSize = sizeof(SYSTEM_RESOURCE_ACPI);
                    break;
                case SystemResourceFrameBuffer:
                    /* FrameBuffer system resource */
                    ResourceSize = sizeof(SYSTEM_RESOURCE_FRAMEBUFFER);
                    break;
                default:
                    /* Unknown system resource type, skip it */
                    ResourceSize = 0;
                    break;
            }

            /* Validate resource size */
            if(ResourceSize != 0 && ResourceSize == ResourceHeader->ResourceSize)
            {
                /* Move valid resource to the internal kernel list of system resources */
                RTL::LinkedList::RemoveEntryList(&ResourceHeader->ListEntry);
                RTL::LinkedList::InsertTailList(&ResourcesListHead, &ResourceHeader->ListEntry);
            }

            /* Go to the next list entry */
            ListEntry = NextListEntry;
        }
    }
}

/**
 * Releases boot system resource.
 *
 * @param ResourceHeader
 *        Specifies a pointer to the boot system resource header.
 *
 * @return This routine does not return any value.
 *
 * @since XT 1.0
 */
XTAPI
VOID
KE::SystemResources::ReleaseResource(IN PSYSTEM_RESOURCE_HEADER ResourceHeader)
{
    /* Disable interrupts and acquire a spinlock */
    AR::CpuFunctions::ClearInterruptFlag();
    KE::SpinLock::AcquireSpinLock(&ResourcesLock);

    /* Release resource lock */
    ResourceHeader->ResourceLocked = FALSE;

    /* Release spinlock and enable interrupts */
    KE::SpinLock::ReleaseSpinLock(&ResourcesLock);
    AR::CpuFunctions::SetInterruptFlag();
}
