/*++

Copyright (c) Microsoft Corporation

Module Name:

    FxResourceCollection.cpp

Abstract:

    This module implements a base object for derived collection classes and
    the derived collection classes.

Author:



Environment:

    Both kernel and user mode

Revision History:

--*/

#include "fxsupportpch.hpp"

extern "C" {
#if defined(EVENT_TRACING)
#include "FxResourceCollection.tmh"
#endif
}

BOOLEAN
FxResourceCollection::RemoveAndDelete(
    __in ULONG Index
    )
/*++

Routine Description:
    Removes an entry from the collection and then deletes it if found.  The
    caller must have removal permissions to perform this action.

Arguments:
    Index - zero based index into the collection at which to perform the removal

Return Value:
    TRUE if the item was found and deleted, FALSE otherwise

  --*/
{
    FxObject* pObject;
    FxCollectionEntry* pEntry;
    KIRQL irql;

    if (IsRemoveAllowed() == FALSE) {
        DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
                            "Removes not allowed on handle %p, remove at index %d"
                            "failed", GetObjectHandle(), Index);

        FxVerifierDbgBreakPoint(GetDriverGlobals());
        return FALSE;
    }

    pObject = NULL;

    Lock(&irql);

    pEntry = FindEntry(Index);
    if (pEntry != NULL) {

        //
        // Mark the list as changed so when we go to create a WDM resource list we
        // know if a new list is needed.
        //
        MarkChanged();

        pObject = pEntry->m_Object;

        //
        // Remove the entry
        //
        RemoveEntry(pEntry);
    }
    Unlock(irql);

    if (pObject != NULL) {
        //
        // Delete the object since we created it
        //
        pObject->DeleteObject();
        pObject = NULL;

        return TRUE;
    }
    else {
        return FALSE;
    }
}

_Must_inspect_result_
NTSTATUS
FxResourceCollection::AddAt(
    __in ULONG Index,
    __in FxObject* Object
    )
/*++

Routine Description:
    Adds an object into the collection at the specified index.

Arguments:
    Index - zero baesd index in which to insert into the list.   WDF_INSERT_AT_END
            is a special value which indicates that the insertion is an append.

    Object - object to add

Return Value:
    NTSTATUS

  --*/
{
    FxCollectionEntry *pNew;
    PLIST_ENTRY ple;
    NTSTATUS status;
    KIRQL irql;

    if (IsAddAllowed() == FALSE) {
        DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGPNP,
                            "Adds not allowed on handle %p, add at index %d"
                            "failed", GetObjectHandle(), Index);

        FxVerifierDbgBreakPoint(GetDriverGlobals());

        return STATUS_ACCESS_DENIED;
    }

    Lock(&irql);

    ple = NULL;
    status = STATUS_SUCCESS;

    pNew = AllocateEntry(GetDriverGlobals());

    if (pNew != NULL) {
        //
        // Inserting at the current count (i.e. one past the end) is the same
        // as append.
        //
        if (Index == WDF_INSERT_AT_END || Index == Count()) {
            ple = &m_ListHead;
        }
        else {
            FxCollectionEntry* cur, *end;
            ULONG i;

            for (cur = Start(), end = End(), i = 0;
                 cur != end;
                 cur = cur->Next(), i++) {
                if (i == Index) {
                    ple = &cur->m_ListEntry;
                    break;
                }
            }

            if (ple == NULL) {
                delete pNew;
                status = STATUS_ARRAY_BOUNDS_EXCEEDED;
            }
        }
    }
    else {
        status = STATUS_INSUFFICIENT_RESOURCES;
    }

    if (NT_SUCCESS(status)) {
        PLIST_ENTRY blink;

        // ple now points to the list entry which we will insert our node
        // *before*

        blink = ple->Blink;

        // Link the previous with the new entry
        blink->Flink = &pNew->m_ListEntry;
        pNew->m_ListEntry.Blink = blink;

        // Link the current with the new entry
        pNew->m_ListEntry.Flink = ple;
        ple->Blink = &pNew->m_ListEntry;

        AddEntry(pNew, Object);

        //
        // Mark the list as changed so when we go to create a WDM resource list
        // we know if a new list is needed.
        //
        MarkChanged();
    }

    Unlock(irql);

    if (!NT_SUCCESS(status)) {
        Object->DeleteFromFailedCreate();
    }

    return status;
}

_Must_inspect_result_
NTSTATUS
FxIoResList::BuildFromWdmList(
    __deref_in PIO_RESOURCE_LIST* WdmResourceList
    )
/*++

Routine Description:
    Builds up the collection with FxResourceIo objects based on the passed in
    WDM io resource list

Arguments:
    WdmResourceList - list which specifies the io resource objects to create

Return Value:
    NTSTATUS

  --*/
{
    PIO_RESOURCE_DESCRIPTOR pWdmDescriptor;
    ULONG i, count;
    NTSTATUS status;

    pWdmDescriptor = &(*WdmResourceList)->Descriptors[0];
    count = (*WdmResourceList)->Count;
    status = STATUS_SUCCESS;

    for (i = 0; i < count; i++) {
        //
        // Now create a new resource object for each resource
        // in our list.
        //
        FxResourceIo *pResource;

        pResource = new(GetDriverGlobals())
            FxResourceIo(GetDriverGlobals(), pWdmDescriptor);

        if (pResource == NULL) {
            //
            // We failed, clean up, and exit.  Since we are only
            // keeping references on the master collection, if
            // we free this, everything else will go away too.
            //
            status = STATUS_INSUFFICIENT_RESOURCES;
        }

        if (NT_SUCCESS(status)) {
            status = pResource->AssignParentObject(this);

            //
            // See notes in previous AssignParentObject as to why
            // we are asserting.
            //
            ASSERT(NT_SUCCESS(status));
            UNREFERENCED_PARAMETER(status);

            status = Add(pResource) ? STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES;
        }

        if (!NT_SUCCESS(status)) {
            break;
        }

        pWdmDescriptor++;
    }

    if (NT_SUCCESS(status)) {
        status = m_OwningList->Add(this) ? STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES;
    }

    if (NT_SUCCESS(status)) {
        *WdmResourceList = (PIO_RESOURCE_LIST) pWdmDescriptor;
    }

    return status;
}

_Must_inspect_result_
NTSTATUS
FxCmResList::BuildFromWdmList(
    __in PCM_RESOURCE_LIST WdmResourceList,
    __in UCHAR AccessFlags
    )
/*++

Routine Description:
    Builds up the collection with FxResourceCm objects based on the passed in
    WDM io resource list.

Arguments:
    WdmResourceList - list which specifies the io resource objects to create

    AccessFlags - permissions to be associated with the list

Return Value:
    NTSTATUS

  --*/
{
    NTSTATUS status;

    //
    // Predispose to success
    //
    status = STATUS_SUCCESS;

    Clear();

    m_AccessFlags = AccessFlags;

    if (WdmResourceList != NULL) {
        PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;
        ULONG count, i;

        //
        // We only expect to see one full resource descriptor.
        //
        ASSERT(WdmResourceList->Count == 1);

        count = WdmResourceList->List[0].PartialResourceList.Count;
        pDescriptor = WdmResourceList->List[0].PartialResourceList.PartialDescriptors;

        for(i = 0; i < count; i++, pDescriptor++) {
            FxResourceCm *pResource;

            pResource = new(GetDriverGlobals())
                FxResourceCm(GetDriverGlobals(), pDescriptor);

            if (pResource == NULL) {
                status = STATUS_INSUFFICIENT_RESOURCES;
            }

            if (NT_SUCCESS(status)) {
                status = pResource->AssignParentObject(this);

                //
                // Since we control our own lifetime here, the assign should
                // always work.
                //
                ASSERT(NT_SUCCESS(status));

                status = Add(pResource) ? STATUS_SUCCESS : STATUS_INSUFFICIENT_RESOURCES;
            }

            if (!NT_SUCCESS(status)) {
                Clear();
                break;
            }
        }
    }

    return status;
}

_Must_inspect_result_
PCM_RESOURCE_LIST
FxCmResList::CreateWdmList(
    __in __drv_strictTypeMatch(__drv_typeExpr) POOL_TYPE PoolType
    )
/*++

Routine Description:
    Allocates and initializes a WDM CM resource list based off of the current
    contents of this collection.

Arguments:
    PoolType - the pool type from which to allocate the resource list

Return Value:
    a new resource list upon success, NULL upon failure

  --*/
{
    PCM_RESOURCE_LIST pWdmResourceList;
    ULONG size;
    PFX_DRIVER_GLOBALS pFxDriverGlobals;

    pWdmResourceList = NULL;
    pFxDriverGlobals = GetDriverGlobals();

    if (Count()) {
        //
        // NOTE: This function assumes all resources are on the same bus
        // and therefore there is only one FULL_RESOURCE_DESCRIPTOR.
        //
        size = sizeof(CM_RESOURCE_LIST) +
               (sizeof(CM_PARTIAL_RESOURCE_DESCRIPTOR) * (Count() - 1));

        pWdmResourceList = (PCM_RESOURCE_LIST)
            MxMemory::MxAllocatePoolWithTag(PoolType, size, pFxDriverGlobals->Tag);

        if (pWdmResourceList != NULL) {
            PCM_PARTIAL_RESOURCE_DESCRIPTOR pDescriptor;
            FxCollectionEntry *cur, *end;

            RtlZeroMemory(pWdmResourceList, size);

            pWdmResourceList->Count = 1;  // We only return one full descriptor

            pWdmResourceList->List[0].PartialResourceList.Version  = 1;
            pWdmResourceList->List[0].PartialResourceList.Revision = 1;
            pWdmResourceList->List[0].PartialResourceList.Count = Count();

            pDescriptor =
                pWdmResourceList->List[0].PartialResourceList.PartialDescriptors;

            end = End();
            for (cur = Start(); cur != end; cur = cur->Next()) {
                FxResourceCm *pResource;

                pResource = (FxResourceCm*) cur->m_Object;

                RtlCopyMemory(pDescriptor,
                              &pResource->m_Descriptor,
                              sizeof(pResource->m_Descriptor));
                pDescriptor++;
            }
        }
    }

    return pWdmResourceList;
}

ULONG
FxCmResList::GetCount(
    VOID
    )
{
    ULONG count;
    KIRQL irql;

    Lock(&irql);
    count = Count();
    Unlock(irql);

    return count;
}

PCM_PARTIAL_RESOURCE_DESCRIPTOR
FxCmResList::GetDescriptor(
    __in ULONG Index
    )
{
    FxResourceCm* pObject;
    KIRQL irql;

    Lock(&irql);
    pObject = (FxResourceCm*) GetItem(Index);
    Unlock(irql);

    if (pObject == NULL) {
        return NULL;
    }
    else {
        //
        // Copy the current descriptor to the clone and return it
        //
        RtlCopyMemory(&pObject->m_DescriptorClone,
                      &pObject->m_Descriptor,
                      sizeof(pObject->m_Descriptor));

        return &pObject->m_DescriptorClone;
    }
}

_Must_inspect_result_
FxIoResReqList*
FxIoResReqList::_CreateFromWdmList(
    __in PFX_DRIVER_GLOBALS FxDriverGlobals,
    __in PIO_RESOURCE_REQUIREMENTS_LIST WdmRequirementsList,
    __in UCHAR AccessFlags
    )
/*++

Routine Description:
    Allocates and populates an FxIoResReqList based on the WDM resource
    requirements list.

Arguments:
    WdmRequirementsList - a list of IO_RESOURCE_LISTs which will indicate how
                          to  fill in the returned collection object

    AccessFlags - permissions to associate with the newly created object

Return Value:
    a new object upon success, NULL upon failure

  --*/

{
    FxIoResReqList* pIoResReqList;
    ULONG i;

    pIoResReqList = new(FxDriverGlobals, WDF_NO_OBJECT_ATTRIBUTES)
        FxIoResReqList(FxDriverGlobals, AccessFlags);

    if (pIoResReqList != NULL) {
        PIO_RESOURCE_LIST pWdmResourceList;
        NTSTATUS status;

        if (WdmRequirementsList == NULL) {
            return pIoResReqList;
        }

        status = STATUS_SUCCESS;
        pWdmResourceList = &WdmRequirementsList->List[0];

        pIoResReqList->m_InterfaceType = WdmRequirementsList->InterfaceType;
        pIoResReqList->m_SlotNumber = WdmRequirementsList->SlotNumber;

        for (i = 0; i < WdmRequirementsList->AlternativeLists; i++) {
            FxIoResList *pResList;

            pResList = new(FxDriverGlobals, WDF_NO_OBJECT_ATTRIBUTES)
                FxIoResList(FxDriverGlobals, pIoResReqList);

            if (pResList != NULL) {
                status = pResList->AssignParentObject(pIoResReqList);

                //
                // Since we control our own lifetime, assigning the parent should
                // never fail.
                //
                ASSERT(NT_SUCCESS(status));

                status = pResList->BuildFromWdmList(&pWdmResourceList);
            }
            else {
                //
                // We failed to allocate a child collection.  Clean up
                // and break out of the loop.
                //
                status = STATUS_INSUFFICIENT_RESOURCES;
            }

            if (!NT_SUCCESS(status)) {
                break;
            }
        }

        if (!NT_SUCCESS(status)) {
            //
            // Cleanup and return a NULL object
            //
            pIoResReqList->DeleteObject();
            pIoResReqList = NULL;
        }
    }

    return pIoResReqList;
}

_Must_inspect_result_
PIO_RESOURCE_REQUIREMENTS_LIST
FxIoResReqList::CreateWdmList(
    VOID
    )
/*++

Routine Description:
    Creates a WDM io resource requirements list based off of the current
    contents of the collection

Arguments:
    None

Return Value:
    new WDM io resource requirements list allocated out of paged pool upon success,
    NULL upon failure or an empty list

  --*/
{
    PIO_RESOURCE_REQUIREMENTS_LIST pRequirementsList;
    FxCollectionEntry *cur, *end;
    NTSTATUS status;
    ULONG totalDescriptors;
    ULONG size;
    ULONG count;
    ULONG tmp;
    PFX_DRIVER_GLOBALS pFxDriverGlobals;

    totalDescriptors = 0;
    pRequirementsList = NULL;

    count = Count();
    pFxDriverGlobals = GetDriverGlobals();

    if (count > 0) {
        //
        // The collection object should contain a set of child collections
        // with each of the various requirement lists.  Use the number of
        // these collections to determine the size of our requirements
        // list.
        //
        end = End();
        for (cur = Start(); cur != end; cur = cur->Next()) {
            status = RtlULongAdd(totalDescriptors,
                                 ((FxIoResList *) cur->m_Object)->Count(),
                                 &totalDescriptors);

            if (!NT_SUCCESS(status)) {
                goto Overflow;
            }
        }

        //
        // We now have enough information to determine how much memory we
        // need to allocate for our requirements list.
        //
        // size = sizeof(IO_RESOURCE_REQUIREMENTS_LIST) +
        //        (sizeof(IO_RESOURCE_LIST) * (count - 1)) +
        //        (sizeof(IO_RESOURCE_DESCRIPTOR) * totalDescriptors) -
        //         (sizeof(IO_RESOURCE_DESCRIPTOR) * count);
        //
        // sizeof(IO_RESOURCE_DESCRIPTOR) * count is subtracted off because
        // each IO_RESOURCE_LIST has an embedded IO_RESOURCE_DESCRIPTOR in it
        // and we don't want to overallocated.
        //

        //
        // To handle overflow each mathematical operation is split out into an
        // overflow safe call.
        //
        size = sizeof(IO_RESOURCE_REQUIREMENTS_LIST);

        // sizeof(IO_RESOURCE_LIST) * (count - 1)
        status = RtlULongMult(sizeof(IO_RESOURCE_LIST), count - 1, &tmp);
        if (!NT_SUCCESS(status)) {
            goto Overflow;
        }

        status = RtlULongAdd(size, tmp, &size);
        if (!NT_SUCCESS(status)) {
            goto Overflow;
        }

        // (sizeof(IO_RESOURCE_DESCRIPTOR) * totalDescriptors)
        status = RtlULongMult(sizeof(IO_RESOURCE_DESCRIPTOR),
                              totalDescriptors,
                              &tmp);
        if (!NT_SUCCESS(status)) {
            goto Overflow;
        }

        status = RtlULongAdd(size, tmp, &size);
        if (!NT_SUCCESS(status)) {
            goto Overflow;
        }

        //  - sizeof(IO_RESOURCE_DESCRIPTOR) * Count() (note the subtraction!)
        status = RtlULongMult(sizeof(IO_RESOURCE_DESCRIPTOR), count, &tmp);
        if (!NT_SUCCESS(status)) {
            goto Overflow;
        }

        // Sub, not Add!
        status = RtlULongSub(size, tmp, &size);
        if (!NT_SUCCESS(status)) {
            goto Overflow;
        }

        pRequirementsList = (PIO_RESOURCE_REQUIREMENTS_LIST)
            MxMemory::MxAllocatePoolWithTag(PagedPool, size, pFxDriverGlobals->Tag);

        if (pRequirementsList != NULL) {
            PIO_RESOURCE_LIST pList;
            FxResourceIo *pResource;

            pList = pRequirementsList->List;

            //
            // Start by zero initializing our structure
            //
            RtlZeroMemory(pRequirementsList, size);

            //
            // InterfaceType and BusNumber are unused for WDM, but InterfaceType
            // is used by the arbiters.
            //
            pRequirementsList->InterfaceType = m_InterfaceType;

            pRequirementsList->SlotNumber = m_SlotNumber;

            //
            // Now populate the requirements list with the resources from
            // our collections.
            //
            pRequirementsList->ListSize = size;
            pRequirementsList->AlternativeLists = Count();

            end = End();
            for (cur = Start(); cur != end; cur = cur->Next()) {
                FxIoResList* pIoResList;
                PIO_RESOURCE_DESCRIPTOR pDescriptor;
                FxCollectionEntry *pIoResCur, *pIoResEnd;

                pIoResList = (FxIoResList*) cur->m_Object;

                pList->Version  = 1;
                pList->Revision = 1;
                pList->Count = pIoResList->Count();

                pDescriptor = pList->Descriptors;

                pIoResEnd = pIoResList->End();
                for (pIoResCur = pIoResList->Start();
                     pIoResCur != pIoResEnd;
                     pIoResCur = pIoResCur->Next()) {

                    pResource = (FxResourceIo *) pIoResCur->m_Object;
                    RtlCopyMemory(pDescriptor,
                                  &pResource->m_Descriptor,
                                  sizeof(pResource->m_Descriptor));
                    pDescriptor++;
                }

                pList = (PIO_RESOURCE_LIST) pDescriptor;
            }
        }
    }

    return pRequirementsList;

Overflow:
    DoTraceLevelMessage(pFxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGPNP,
                        "Integer overflow occured when computing size of "
                        "IO_RESOURCE_REQUIREMENTS_LIST");

    return NULL;
}