/*
 * PROJECT:         ReactOS Kernel
 * COPYRIGHT:       GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * FILE:            ntoskrnl/io/debug.c
 * PURPOSE:         Useful functions for debugging IO and PNP managers
 * PROGRAMMERS:     Copyright 2020 Vadim Galyant <vgal@rambler.ru>
 */

#include <ntoskrnl.h>
#include "pnpio.h"

#define NDEBUG
#include <debug.h>

/* GLOBALS ********************************************************************/

extern PDEVICE_NODE IopRootDeviceNode;

/* FUNCTIONS ******************************************************************/

/* CmResource */

/* PipDumpCmResourceDescriptor() displays information about a Cm Descriptor

   DebugLevel: 0 - always dump
               1 - dump if not defined NDEBUG
*/
VOID
NTAPI
PipDumpCmResourceDescriptor(
    _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor,
    _In_ ULONG DebugLevel)
{
    PAGED_CODE();

    if (DebugLevel != 0)
    {
#ifdef NDEBUG
        return;
#endif
    }

    if (Descriptor == NULL)
    {
        DPRINT1("Dump CmDescriptor: Descriptor == NULL\n");
        return;
    }

    switch (Descriptor->Type)
    {
        case CmResourceTypePort:
            DPRINT1("[%p:%X:%X] IO:  Start %X:%X, Len %X\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.Port.Start.HighPart, Descriptor->u.Port.Start.LowPart, Descriptor->u.Port.Length);
            break;

        case CmResourceTypeInterrupt:
            DPRINT1("[%p:%X:%X] INT: Lev %X Vec %X Aff %IX\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.Interrupt.Level, Descriptor->u.Interrupt.Vector, Descriptor->u.Interrupt.Affinity);
            break;

        case CmResourceTypeMemory:
            DPRINT1("[%p:%X:%X] MEM: %X:%X Len %X\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.Memory.Start.HighPart, Descriptor->u.Memory.Start.LowPart, Descriptor->u.Memory.Length);
            break;

        case CmResourceTypeDma:
            DPRINT1("[%p:%X:%X] DMA: Channel %X Port %X\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.Dma.Channel, Descriptor->u.Dma.Port);
            break;

        case CmResourceTypeDeviceSpecific:
            DPRINT1("[%p:%X:%X] DAT: DataSize %X\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.DeviceSpecificData.DataSize);
            break;

        case CmResourceTypeBusNumber:
            DPRINT1("[%p:%X:%X] BUS: Start %X Len %X Reserv %X\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.BusNumber.Start, Descriptor->u.BusNumber.Length, Descriptor->u.BusNumber.Reserved);
            break;

        case CmResourceTypeDevicePrivate:
            DPRINT1("[%p:%X:%X] PVT: D[0] %X D[1] %X D[2] %X\n", Descriptor, Descriptor->ShareDisposition, Descriptor->Flags, Descriptor->u.DevicePrivate.Data[0], Descriptor->u.DevicePrivate.Data[1], Descriptor->u.DevicePrivate.Data[2]);
            break;

        default:
            DPRINT1("[%p] Unknown type %X\n", Descriptor, Descriptor->Type);
            break;
    }
}

/* PipGetNextCmPartialDescriptor() return poiner to next a Cm Descriptor */

PCM_PARTIAL_RESOURCE_DESCRIPTOR
NTAPI
PipGetNextCmPartialDescriptor(
    _In_ PCM_PARTIAL_RESOURCE_DESCRIPTOR CmDescriptor)
{
    PCM_PARTIAL_RESOURCE_DESCRIPTOR NextDescriptor;

    /* Assume the descriptors are the fixed size ones */
    NextDescriptor = CmDescriptor + 1;

    /* But check if this is actually a variable-sized descriptor */
    if (CmDescriptor->Type == CmResourceTypeDeviceSpecific)
    {
        /* Add the size of the variable section as well */
        NextDescriptor = (PVOID)((ULONG_PTR)NextDescriptor +
                                 CmDescriptor->u.DeviceSpecificData.DataSize);
    }

    /* Now the correct pointer has been computed, return it */
    return NextDescriptor;
}

/* PipDumpCmResourceList() displays information about a Cm List

   DebugLevel: 0 - always dump
               1 - dump if not defined NDEBUG
*/
VOID
NTAPI
PipDumpCmResourceList(
    _In_ PCM_RESOURCE_LIST CmResource,
    _In_ ULONG DebugLevel)
{
    PCM_FULL_RESOURCE_DESCRIPTOR FullList;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR Descriptor;
    ULONG ix;
    ULONG jx;

    PAGED_CODE();

    if (DebugLevel != 0)
    {
#ifdef NDEBUG
        return;
#endif
    }

    DPRINT1("Dump CmList: CmResource %p\n", CmResource);

    if (CmResource == NULL)
    {
        DPRINT1("PipDumpCmResourceList: CmResource == NULL\n");
        return;
    }

    if (CmResource->Count == 0)
    {
        DPRINT1("PipDumpCmResourceList: CmResource->Count == 0\n");
        return;
    }

    DPRINT1("FullList Count %x\n", CmResource->Count);

    FullList = &CmResource->List[0];

    for (ix = 0; ix < CmResource->Count; ix++)
    {
        DPRINT1("List #%X Iface %X Bus #%X Ver.%X Rev.%X Count %X\n",
                ix,
                FullList->InterfaceType,
                FullList->BusNumber,
                FullList->PartialResourceList.Version,
                FullList->PartialResourceList.Revision,
                FullList->PartialResourceList.Count);

        Descriptor = FullList->PartialResourceList.PartialDescriptors;

        for (jx = 0; jx < FullList->PartialResourceList.Count; jx++)
        {
            PipDumpCmResourceDescriptor(Descriptor, DebugLevel);
            Descriptor = PipGetNextCmPartialDescriptor(Descriptor);
        }

        FullList = (PCM_FULL_RESOURCE_DESCRIPTOR)Descriptor;
    }
}

/* IoResource */

/* PipDumpIoResourceDescriptor() displays information about a Io Descriptor

   DebugLevel: 0 - always dump
               1 - dump if not defined NDEBUG
*/
VOID
NTAPI
PipDumpIoResourceDescriptor(
    _In_ PIO_RESOURCE_DESCRIPTOR Descriptor,
    _In_ ULONG DebugLevel)
{
    PAGED_CODE();

    if (DebugLevel != 0)
    {
#ifdef NDEBUG
        return;
#endif
    }

    if (Descriptor == NULL)
    {
        DPRINT1("DumpResourceDescriptor: Descriptor == 0\n");
        return;
    }

    switch (Descriptor->Type)
    {
        case CmResourceTypeNull:
            DPRINT1("[%p:%X:%X] O: Len %X Align %X Min %I64X, Max %I64X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.Generic.Length, Descriptor->u.Generic.Alignment, Descriptor->u.Generic.MinimumAddress.QuadPart, Descriptor->u.Generic.MaximumAddress.QuadPart);
            break;

        case CmResourceTypePort:
            DPRINT1("[%p:%X:%X] IO: Min %X:%X, Max %X:%X, Align %X Len %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.Port.MinimumAddress.HighPart, Descriptor->u.Port.MinimumAddress.LowPart, Descriptor->u.Port.MaximumAddress.HighPart, Descriptor->u.Port.MaximumAddress.LowPart, Descriptor->u.Port.Alignment, Descriptor->u.Port.Length);
            break;

        case CmResourceTypeInterrupt:
            DPRINT1("[%p:%X:%X] INT: Min %X Max %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.Interrupt.MinimumVector, Descriptor->u.Interrupt.MaximumVector);
            break;

        case CmResourceTypeMemory:
            DPRINT1("[%p:%X:%X] MEM: Min %X:%X, Max %X:%X, Align %X Len %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.Memory.MinimumAddress.HighPart, Descriptor->u.Memory.MinimumAddress.LowPart, Descriptor->u.Memory.MaximumAddress.HighPart, Descriptor->u.Memory.MaximumAddress.LowPart, Descriptor->u.Memory.Alignment, Descriptor->u.Memory.Length);
            break;

        case CmResourceTypeDma:
            DPRINT1("[%p:%X:%X] DMA: Min %X Max %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.Dma.MinimumChannel, Descriptor->u.Dma.MaximumChannel);
            break;

        case CmResourceTypeBusNumber:
            DPRINT1("[%p:%X:%X] BUS: Min %X Max %X Len %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.BusNumber.MinBusNumber, Descriptor->u.BusNumber.MaxBusNumber, Descriptor->u.BusNumber.Length);
            break;

        case CmResourceTypeConfigData:
            DPRINT1("[%p:%X:%X] CFG: Priority %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.ConfigData.Priority);
            break;

        case CmResourceTypeDevicePrivate:
            DPRINT1("[%p:%X:%X] DAT: %X %X %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->u.DevicePrivate.Data[0], Descriptor->u.DevicePrivate.Data[1], Descriptor->u.DevicePrivate.Data[2]);
            break;

        default:
            DPRINT1("[%p:%X:%X]. Unknown type %X\n", Descriptor, Descriptor->Option, Descriptor->ShareDisposition, Descriptor->Type);
            break;
    }
}

/* PipDumpResourceRequirementsList() displays information about a Io List

   DebugLevel: 0 - always dump
               1 - dump if not defined NDEBUG
*/
VOID
NTAPI
PipDumpResourceRequirementsList(
    _In_ PIO_RESOURCE_REQUIREMENTS_LIST IoResource,
    _In_ ULONG DebugLevel)
{
    PIO_RESOURCE_LIST AltList;
    PIO_RESOURCE_DESCRIPTOR Descriptor;
    ULONG ix;
    ULONG jx;

    PAGED_CODE();

    if (DebugLevel != 0)
    {
#ifdef NDEBUG
        return;
#endif
    }

    if (IoResource == NULL)
    {
        DPRINT1("PipDumpResourceRequirementsList: IoResource == 0\n");
        return;
    }

    DPRINT1("Dump RequirementsList: IoResource %p\n", IoResource);
    DPRINT1("Interface %X Bus %X Slot %X AlternativeLists %X\n",
            IoResource->InterfaceType,
            IoResource->BusNumber,
            IoResource->SlotNumber,
            IoResource->AlternativeLists);

    AltList = &IoResource->List[0];

    if (IoResource->AlternativeLists < 1)
    {
        DPRINT1("PipDumpResourceRequirementsList: AlternativeLists < 1\n");
        return;
    }

    for (ix = 0; ix < IoResource->AlternativeLists; ix++)
    {
        DPRINT1("AltList %p, AltList->Count %X\n", AltList, AltList->Count);

        for (jx = 0; jx < AltList->Count; jx++)
        {
            Descriptor = &AltList->Descriptors[jx];
            PipDumpIoResourceDescriptor(Descriptor, DebugLevel);
        }

        AltList = (PIO_RESOURCE_LIST)(AltList->Descriptors + AltList->Count);
        DPRINT1("End Descriptors %p\n", AltList);
    }
}

/* Gets the name of the device node state. */

PWSTR
NTAPI
PipGetDeviceNodeStateName(
    _In_ PNP_DEVNODE_STATE State)
{
    switch (State)
    {
        case DeviceNodeUnspecified:
            return L"DeviceNodeUnspecified";

        case DeviceNodeUninitialized:
            return L"DeviceNodeUninitialized";

        case DeviceNodeInitialized:
            return L"DeviceNodeInitialized";

        case DeviceNodeDriversAdded:
            return L"DeviceNodeDriversAdded";

        case DeviceNodeResourcesAssigned:
            return L"DeviceNodeResourcesAssigned";

        case DeviceNodeStartPending:
            return L"DeviceNodeStartPending";

        case DeviceNodeStartCompletion:
            return L"DeviceNodeStartCompletion";

        case DeviceNodeStartPostWork:
            return L"DeviceNodeStartPostWork";

        case DeviceNodeStarted:
            return L"DeviceNodeStarted";

        case DeviceNodeQueryStopped:
            return L"DeviceNodeQueryStopped";

        case DeviceNodeStopped:
            return L"DeviceNodeStopped";

        case DeviceNodeRestartCompletion:
            return L"DeviceNodeRestartCompletion";

        case DeviceNodeEnumeratePending:
            return L"DeviceNodeEnumeratePending";

        case DeviceNodeEnumerateCompletion:
            return L"DeviceNodeEnumerateCompletion";

        case DeviceNodeAwaitingQueuedDeletion:
            return L"DeviceNodeAwaitingQueuedDeletion";

        case DeviceNodeAwaitingQueuedRemoval:
            return L"DeviceNodeAwaitingQueuedRemoval";

        case DeviceNodeQueryRemoved:
            return L"DeviceNodeQueryRemoved";

        case DeviceNodeRemovePendingCloses:
            return L"DeviceNodeRemovePendingCloses";

        case DeviceNodeRemoved:
            return L"DeviceNodeRemoved";

        case DeviceNodeDeletePendingCloses:
            return L"DeviceNodeDeletePendingCloses";

        case DeviceNodeDeleted:
            return L"DeviceNodeDeleted";

        default:
            break;
    }

    if (State != MaxDeviceNodeState)
    {
        DPRINT1("PipGetDeviceNodeStateName: Unknown State %X\n", State);
    }

    return L"";
}

/* Dump list arbiters for the device node */

VOID
NTAPI
PipDumpArbiters(
    _In_ PDEVICE_NODE DeviceNode,
    _In_ ULONG NodeLevel)
{
    DPRINT("Level %X DevNode %p for PDO %p\n", NodeLevel, DeviceNode, DeviceNode->PhysicalDeviceObject);

    /* Definitions needed for arbiter */
    UNIMPLEMENTED;
}

/* PipDumpDeviceNode() displays information about a device node

    Flags is bitmask parametr:
        1 - traversal of all children nodes
        2 - dump allocated resources and boot configuration resources
        4 - dump resources required
        8 - dump translated resources

    DebugLevel: 0 - always dump
                1 - dump if not defined NDEBUG
*/
VOID
NTAPI
PipDumpDeviceNode(
    _In_ PDEVICE_NODE DeviceNode,
    _In_ ULONG NodeLevel,
    _In_ ULONG Flags,
    _In_ ULONG DebugLevel)
{
    PDEVICE_NODE ChildDeviceNode;

    if (DebugLevel != 0)
    {
#ifdef NDEBUG
        return;
#endif
    }

    DPRINT1("* Level %X DevNode %p for PDO %p\n", NodeLevel, DeviceNode, DeviceNode->PhysicalDeviceObject);
    DPRINT1("Instance    %wZ\n", &DeviceNode->InstancePath);
if (DeviceNode->ServiceName.Length)
    DPRINT1("Service     %wZ\n", &DeviceNode->ServiceName);
#if 0
    /* It is not used yet */
    DPRINT1("State       %X %S\n", DeviceNode->State, PipGetDeviceNodeStateName(DeviceNode->State));
    DPRINT1("Prev State  %X %S\n", DeviceNode->PreviousState, PipGetDeviceNodeStateName(DeviceNode->PreviousState));
#endif
if (DeviceNode->Problem)
    DPRINT1("Problem     %X\n", DeviceNode->Problem);
#if 0
    /* It is not implemeted yet */
    PipDumpArbiters(DeviceNode, NodeLevel);
#endif

    /* Allocated resources and  Boot configuration (reported by IRP_MN_QUERY_RESOURCES)*/
    if (Flags & PIP_DUMP_FL_RES_ALLOCATED)
    {
        if (DeviceNode->ResourceList)
        {
            DPRINT1("---------- ResourceList ----------\n");
            PipDumpCmResourceList(DeviceNode->ResourceList, DebugLevel);
        }

        if (DeviceNode->BootResources)
        {
            DPRINT1("---------- BootResources ----------\n");
            PipDumpCmResourceList(DeviceNode->BootResources, DebugLevel);
        }
    }

    /* Resources required (reported by IRP_MN_FILTER_RESOURCE_REQUIREMENTS) */
    if (Flags & PIP_DUMP_FL_RES_REQUIREMENTS)
    {
        if (DeviceNode->ResourceRequirements)
        {
            DPRINT1("---------- ResourceRequirements ----------\n");
            PipDumpResourceRequirementsList(DeviceNode->ResourceRequirements, DebugLevel);
        }
    }

    /* Translated resources (AllocatedResourcesTranslated)  */
    if (Flags & PIP_DUMP_FL_RES_TRANSLATED)
    {
        if (DeviceNode->ResourceListTranslated)
        {
            DPRINT1("---------- ResourceListTranslated ----------\n");
            PipDumpCmResourceList(DeviceNode->ResourceListTranslated, DebugLevel);
        }
    }

    /* Traversal of all children nodes */
    if (Flags & PIP_DUMP_FL_ALL_NODES)
    {
        for (ChildDeviceNode = DeviceNode->Child;
             ChildDeviceNode != NULL;
             ChildDeviceNode = ChildDeviceNode->Sibling)
        {
            /* Recursive call */
            PipDumpDeviceNode(ChildDeviceNode, (NodeLevel + 1), Flags, DebugLevel);
        }
    }
}

/* PipDumpDeviceNodes() displays information about a node(s) in the device tree

    If DeviceNode is NULL, then the dump starts at the beginning of the device tree.

    Flags is bitmask parametr:
        1 - traversal of all children nodes
        2 - dump allocated resources and boot configuration resources
        4 - dump resources required
        8 - dump translated resources

    DebugLevel: 0 - always dump
                1 - dump if not defined NDEBUG

    See also: Windows DDK -> General Driver Information ->
        Kernel-Mode Driver Architecture -> Design Guide -> Plug and Play ->
        Introduction to Plug and Play -> Hardware Resources
*/
VOID
NTAPI
PipDumpDeviceNodes(
    _In_ PDEVICE_NODE DeviceNode,
    _In_ ULONG Flags,
    _In_ ULONG DebugLevel)
{
    if (DebugLevel != 0)
    {
#ifdef NDEBUG
        return;
#endif
    }

    DPRINT1("PipDumpDeviceNodes: DeviceNode %X, Flags %X Level %X\n", DeviceNode, Flags, DebugLevel);

    if (DeviceNode == NULL)
    {
        DeviceNode = IopRootDeviceNode;
    }

    PipDumpDeviceNode(DeviceNode, 0, Flags, DebugLevel);
}

/* EOF */