/*
 * PROJECT:     ReactOS USB Port Driver
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     USBPort interface functions
 * COPYRIGHT:   Copyright 2017 Vadim Galyant <vgal@rambler.ru>
 */

#include "usbport.h"

#define NDEBUG
#include <debug.h>

VOID
USB_BUSIFFN
USBI_InterfaceReference(IN PVOID BusContext)
{
    DPRINT("USBI_InterfaceReference\n");
}

VOID
USB_BUSIFFN
USBI_InterfaceDereference(IN PVOID BusContext)
{
    DPRINT("USBI_InterfaceDereference\n");
}

/* USB port driver Interface functions */

NTSTATUS
USB_BUSIFFN
USBHI_CreateUsbDevice(IN PVOID BusContext,
                      IN OUT PUSB_DEVICE_HANDLE *UsbdDeviceHandle,
                      IN PUSB_DEVICE_HANDLE UsbdHubDeviceHandle,
                      IN USHORT PortStatus,
                      IN USHORT PortNumber)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PUSB_DEVICE_HANDLE deviceHandle = NULL;
    NTSTATUS Status;

    DPRINT("USBHI_CreateUsbDevice: ...\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;

    Status = USBPORT_CreateDevice(&deviceHandle,
                                  PdoExtension->FdoDevice,
                                  (PUSBPORT_DEVICE_HANDLE)UsbdHubDeviceHandle,
                                  PortStatus,
                                  PortNumber);

    *UsbdDeviceHandle = deviceHandle;

    return Status;
}

NTSTATUS
USB_BUSIFFN
USBHI_InitializeUsbDevice(IN PVOID BusContext,
                          OUT PUSB_DEVICE_HANDLE UsbdDeviceHandle)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;

    DPRINT("USBHI_InitializeUsbDevice\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;

    return USBPORT_InitializeDevice((PUSBPORT_DEVICE_HANDLE)UsbdDeviceHandle,
                                    PdoExtension->FdoDevice);
}

NTSTATUS
USB_BUSIFFN
USBHI_GetUsbDescriptors(IN PVOID BusContext,
                        IN PUSB_DEVICE_HANDLE UsbdDeviceHandle,
                        IN PUCHAR DeviceDescBuffer,
                        IN PULONG DeviceDescBufferLen,
                        IN PUCHAR ConfigDescBuffer,
                        IN PULONG ConfigDescBufferLen)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PUSBPORT_DEVICE_HANDLE DeviceHandle;

    NTSTATUS Status;

    DPRINT("USBHI_GetUsbDescriptors ...\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    DeviceHandle = (PUSBPORT_DEVICE_HANDLE)UsbdDeviceHandle;

    if (DeviceDescBuffer && *DeviceDescBufferLen)
    {
        if (*DeviceDescBufferLen > sizeof(USB_DEVICE_DESCRIPTOR))
            *DeviceDescBufferLen = sizeof(USB_DEVICE_DESCRIPTOR);

        RtlCopyMemory(DeviceDescBuffer,
                      &DeviceHandle->DeviceDescriptor,
                      *DeviceDescBufferLen);
    }

    Status = USBPORT_GetUsbDescriptor(DeviceHandle,
                                      PdoExtension->FdoDevice,
                                      USB_CONFIGURATION_DESCRIPTOR_TYPE,
                                      ConfigDescBuffer,
                                      ConfigDescBufferLen);

    USBPORT_DumpingDeviceDescriptor((PUSB_DEVICE_DESCRIPTOR)DeviceDescBuffer);

    return Status;
}

NTSTATUS
USB_BUSIFFN
USBHI_RemoveUsbDevice(IN PVOID BusContext,
                      IN OUT PUSB_DEVICE_HANDLE UsbdDeviceHandle,
                      IN ULONG Flags)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;

    DPRINT("USBHI_RemoveUsbDevice: UsbdDeviceHandle - %p, Flags - %x\n",
           UsbdDeviceHandle,
           Flags);

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;

    return USBPORT_RemoveDevice(PdoExtension->FdoDevice,
                                (PUSBPORT_DEVICE_HANDLE)UsbdDeviceHandle,
                                Flags);
}

NTSTATUS
USB_BUSIFFN
USBHI_RestoreUsbDevice(IN PVOID BusContext,
                       OUT PUSB_DEVICE_HANDLE OldUsbdDeviceHandle,
                       OUT PUSB_DEVICE_HANDLE NewUsbdDeviceHandle)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;

    DPRINT("USBHI_RestoreUsbDevice: OldUsbdDeviceHandle - %p, NewUsbdDeviceHandle - %x\n",
           OldUsbdDeviceHandle,
           NewUsbdDeviceHandle);

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;

    return USBPORT_RestoreDevice(PdoExtension->FdoDevice,
                                (PUSBPORT_DEVICE_HANDLE)OldUsbdDeviceHandle,
                                (PUSBPORT_DEVICE_HANDLE)NewUsbdDeviceHandle);
}

NTSTATUS
USB_BUSIFFN
USBHI_QueryDeviceInformation(IN PVOID BusContext,
                             IN PUSB_DEVICE_HANDLE UsbdDeviceHandle,
                             OUT PVOID DeviceInfoBuffer,
                             IN ULONG DeviceInfoBufferLen,
                             OUT PULONG LenDataReturned)
{
    PUSB_DEVICE_INFORMATION_0 DeviceInfo;
    PUSBPORT_CONFIGURATION_HANDLE ConfigHandle;
    PLIST_ENTRY InterfaceEntry;
    PUSBPORT_DEVICE_HANDLE DeviceHandle;
    ULONG NumberOfOpenPipes = 0;
    PUSB_PIPE_INFORMATION_0 PipeInfo;
    PUSBPORT_PIPE_HANDLE PipeHandle;
    PUSBPORT_INTERFACE_HANDLE InterfaceHandle;
    ULONG ActualLength;
    ULONG ix;

    DPRINT("USBHI_QueryDeviceInformation: ...\n");

    *LenDataReturned = 0;

    if (DeviceInfoBufferLen < sizeof(USB_LEVEL_INFORMATION))
    {
        return STATUS_BUFFER_TOO_SMALL;
    }

    DeviceInfo = DeviceInfoBuffer;

    if (DeviceInfo->InformationLevel > 0)
    {
        return STATUS_NOT_SUPPORTED;
    }

    DeviceHandle = UsbdDeviceHandle;
    ConfigHandle = DeviceHandle->ConfigHandle;

    if (ConfigHandle)
    {
        InterfaceEntry = ConfigHandle->InterfaceHandleList.Flink;

        while (InterfaceEntry &&
               InterfaceEntry != &ConfigHandle->InterfaceHandleList)
        {
            InterfaceHandle = CONTAINING_RECORD(InterfaceEntry,
                                                USBPORT_INTERFACE_HANDLE,
                                                InterfaceLink);

            NumberOfOpenPipes += InterfaceHandle->InterfaceDescriptor.bNumEndpoints;

            InterfaceEntry = InterfaceEntry->Flink;
        }
    }

    ActualLength = FIELD_OFFSET(USB_DEVICE_INFORMATION_0, PipeList) +
                   NumberOfOpenPipes * sizeof(USB_PIPE_INFORMATION_0);

    if (DeviceInfoBufferLen < ActualLength)
    {
        DeviceInfo->ActualLength = ActualLength;
        *LenDataReturned = sizeof(USB_LEVEL_INFORMATION);

        return STATUS_BUFFER_TOO_SMALL;
    }

    RtlZeroMemory(DeviceInfo, ActualLength);

    DeviceInfo->InformationLevel = 0;
    DeviceInfo->ActualLength = ActualLength;
    DeviceInfo->DeviceAddress = DeviceHandle->DeviceAddress;
    DeviceInfo->NumberOfOpenPipes = NumberOfOpenPipes;
    DeviceInfo->DeviceSpeed = DeviceHandle->DeviceSpeed;

    RtlCopyMemory(&DeviceInfo->DeviceDescriptor,
                  &DeviceHandle->DeviceDescriptor,
                  sizeof(USB_DEVICE_DESCRIPTOR));

    USBPORT_DumpingDeviceDescriptor(&DeviceInfo->DeviceDescriptor);

    if (DeviceHandle->DeviceSpeed == UsbFullSpeed ||
        DeviceHandle->DeviceSpeed == UsbLowSpeed)
    {
        DeviceInfo->DeviceType = Usb11Device;
    }
    else if (DeviceHandle->DeviceSpeed == UsbHighSpeed)
    {
        DeviceInfo->DeviceType = Usb20Device;
    }

    DeviceInfo->CurrentConfigurationValue = 0;

    if (!ConfigHandle)
    {
        *LenDataReturned = ActualLength;
        return STATUS_SUCCESS;
    }

    DeviceInfo->CurrentConfigurationValue =
        ConfigHandle->ConfigurationDescriptor->bConfigurationValue;

    InterfaceEntry = ConfigHandle->InterfaceHandleList.Flink;

    while (InterfaceEntry &&
           InterfaceEntry != &ConfigHandle->InterfaceHandleList)
    {
        InterfaceHandle = CONTAINING_RECORD(InterfaceEntry,
                                            USBPORT_INTERFACE_HANDLE,
                                            InterfaceLink);

        if (InterfaceHandle->InterfaceDescriptor.bNumEndpoints > 0)
        {
            PipeInfo = &DeviceInfo->PipeList[0];
            PipeHandle = &InterfaceHandle->PipeHandle[0];

            for (ix = 0;
                 ix < InterfaceHandle->InterfaceDescriptor.bNumEndpoints;
                 ix++)
            {
                if (PipeHandle->Flags & PIPE_HANDLE_FLAG_NULL_PACKET_SIZE)
                {
                    PipeInfo->ScheduleOffset = 1;
                }
                else
                {
                    PipeInfo->ScheduleOffset =
                        PipeHandle->Endpoint->EndpointProperties.ScheduleOffset;
                }

                RtlCopyMemory(&PipeInfo->EndpointDescriptor,
                              &PipeHandle->EndpointDescriptor,
                              sizeof(USB_ENDPOINT_DESCRIPTOR));

                PipeInfo += 1;
                PipeHandle += 1;
            }
        }

        InterfaceEntry = InterfaceEntry->Flink;
    }

    *LenDataReturned = ActualLength;

    return STATUS_SUCCESS;
}

NTSTATUS
USB_BUSIFFN
USBHI_GetControllerInformation(IN PVOID BusContext,
                               OUT PVOID ControllerInfoBuffer,
                               IN ULONG ControllerInfoBufferLen,
                               OUT PULONG LenDataReturned)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSB_CONTROLLER_INFORMATION_0 InfoBuffer;
    NTSTATUS Status;

    DPRINT("USBHI_GetControllerInformation: ControllerInfoBufferLen - %x\n",
           ControllerInfoBufferLen);

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    FdoDevice = PdoExtension->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;

    InfoBuffer = ControllerInfoBuffer;

    *LenDataReturned = 0;

    if (ControllerInfoBufferLen < sizeof(USB_LEVEL_INFORMATION))
    {
        Status = STATUS_BUFFER_TOO_SMALL;
        return Status;
    }

    *LenDataReturned = sizeof(USB_LEVEL_INFORMATION);

    if (InfoBuffer->InformationLevel > 0)
    {
        Status = STATUS_NOT_SUPPORTED;
        return Status;
    }

    InfoBuffer->ActualLength = sizeof(USB_CONTROLLER_INFORMATION_0);

    if (ControllerInfoBufferLen >= sizeof(USB_CONTROLLER_INFORMATION_0))
    {
        InfoBuffer->SelectiveSuspendEnabled =
            (FdoExtension->Flags & USBPORT_FLAG_SELECTIVE_SUSPEND) ==
            USBPORT_FLAG_SELECTIVE_SUSPEND;
    }

    *LenDataReturned = sizeof(USB_CONTROLLER_INFORMATION_0);

    return STATUS_SUCCESS;
}

NTSTATUS
USB_BUSIFFN
USBHI_ControllerSelectiveSuspend(IN PVOID BusContext,
                                 IN BOOLEAN Enable)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    ULONG Flags;
    ULONG HcDisable;
    NTSTATUS Status;

    DPRINT("USBHI_ControllerSelectiveSuspend: Enable - %x\n", Enable);

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    FdoDevice = PdoExtension->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;

    Flags = FdoExtension->Flags;

    if (Flags & USBPORT_FLAG_BIOS_DISABLE_SS)
    {
        return STATUS_SUCCESS;
    }

    if (Enable)
    {
        FdoExtension->Flags |= USBPORT_FLAG_SELECTIVE_SUSPEND;
        HcDisable = 0;
    }
    else
    {
        FdoExtension->Flags &= ~USBPORT_FLAG_SELECTIVE_SUSPEND;
        HcDisable = 1;
    }

    Status = USBPORT_SetRegistryKeyValue(FdoExtension->CommonExtension.LowerPdoDevice,
                                         TRUE,
                                         REG_DWORD,
                                         L"HcDisableSelectiveSuspend",
                                         &HcDisable,
                                         sizeof(HcDisable));

    if (NT_SUCCESS(Status))
    {
        if (Enable)
            FdoExtension->Flags |= USBPORT_FLAG_SELECTIVE_SUSPEND;
        else
            FdoExtension->Flags &= ~USBPORT_FLAG_SELECTIVE_SUSPEND;
    }

    return Status;
}

NTSTATUS
USB_BUSIFFN
USBHI_GetExtendedHubInformation(IN PVOID BusContext,
                                IN PDEVICE_OBJECT HubPhysicalDeviceObject,
                                IN OUT PVOID HubInformationBuffer,
                                IN ULONG HubInfoLen,
                                IN OUT PULONG LenDataReturned)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    ULONG NumPorts;
    ULONG ix;
    PUSB_EXTHUB_INFORMATION_0 HubInfoBuffer;
    USB_PORT_STATUS_AND_CHANGE PortStatus;
    ULONG PortAttrX;

    DPRINT("USBHI_GetExtendedHubInformation: ...\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    FdoDevice = PdoExtension->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    HubInfoBuffer = HubInformationBuffer;
    PortStatus.AsUlong32 = 0;

    if (HubPhysicalDeviceObject != PdoDevice)
    {
        *LenDataReturned = 0;
        return STATUS_NOT_SUPPORTED;
    }

    if (HubInfoLen < sizeof(USB_EXTHUB_INFORMATION_0))
    {
        *LenDataReturned = 0;
        return STATUS_BUFFER_TOO_SMALL;
    }

    NumPorts = PdoExtension->RootHubDescriptors->Descriptor.bNumberOfPorts;
    HubInfoBuffer->NumberOfPorts = NumPorts;

    if (NumPorts == 0)
    {
        *LenDataReturned = sizeof(USB_EXTHUB_INFORMATION_0);
        return STATUS_SUCCESS;
    }

    for (ix = 0; ix < HubInfoBuffer->NumberOfPorts; ++ix)
    {
        HubInfoBuffer->Port[ix].PhysicalPortNumber = ix + 1;
        HubInfoBuffer->Port[ix].PortLabelNumber = ix;
        HubInfoBuffer->Port[ix].VidOverride = 0;
        HubInfoBuffer->Port[ix].PidOverride = 0;
        HubInfoBuffer->Port[ix].PortAttributes = 0;

        if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2)
        {
            HubInfoBuffer->Port[ix].PortAttributes = USB_PORTATTR_SHARED_USB2;

            Packet->RH_GetPortStatus(FdoExtension->MiniPortExt,
                                     ix + 1,
                                     &PortStatus);

            if (PortStatus.PortStatus.Usb20PortStatus.AsUshort16 & 0x8000)
            {
                HubInfoBuffer->Port[ix].PortAttributes |= USB_PORTATTR_OWNED_BY_CC;
            }
        }
        else
        {
            if (!(FdoExtension->Flags & USBPORT_FLAG_COMPANION_HC))
            {
                continue;
            }

            if (USBPORT_FindUSB2Controller(FdoDevice))
            {
                HubInfoBuffer->Port[ix].PortAttributes |= USB_PORTATTR_NO_OVERCURRENT_UI;
            }
        }
    }

    for (ix = 0; ix < HubInfoBuffer->NumberOfPorts; ++ix)
    {
        PortAttrX = 0;

        USBPORT_GetRegistryKeyValueFullInfo(FdoDevice,
                                            FdoExtension->CommonExtension.LowerPdoDevice,
                                            FALSE,
                                            L"PortAttrX",
                                            sizeof(L"PortAttrX"),
                                            &PortAttrX,
                                            sizeof(PortAttrX));

        HubInfoBuffer->Port[ix].PortAttributes |= PortAttrX;
    }

    *LenDataReturned = sizeof(USB_EXTHUB_INFORMATION_0);

    return STATUS_SUCCESS;
}

NTSTATUS
USB_BUSIFFN
USBHI_GetRootHubSymbolicName(IN PVOID BusContext,
                             IN OUT PVOID HubInfoBuffer,
                             IN ULONG HubInfoBufferLen,
                             OUT PULONG HubNameActualLen)
{
    PDEVICE_OBJECT PdoDevice;
    UNICODE_STRING HubName;
    PUNICODE_STRING InfoBuffer;
    NTSTATUS Status;

    DPRINT("USBHI_GetRootHubSymbolicName: ...\n");

    PdoDevice = BusContext;

    Status = USBPORT_GetSymbolicName(PdoDevice, &HubName);

    if (HubInfoBufferLen < HubName.Length)
    {
        InfoBuffer = HubInfoBuffer;
        InfoBuffer->Length = 0;
    }
    else
    {
        RtlCopyMemory(HubInfoBuffer, HubName.Buffer, HubName.Length);
    }

    *HubNameActualLen = HubName.Length;

    if (NT_SUCCESS(Status))
        RtlFreeUnicodeString(&HubName);

    return Status;
}

PVOID
USB_BUSIFFN
USBHI_GetDeviceBusContext(IN PVOID BusContext,
                          IN PVOID DeviceHandle)
{
    DPRINT1("USBHI_GetDeviceBusContext: UNIMPLEMENTED. FIXME.\n");
    return NULL;
}

NTSTATUS
USB_BUSIFFN
USBHI_Initialize20Hub(IN PVOID BusContext,
                      IN PUSB_DEVICE_HANDLE UsbdHubDeviceHandle,
                      IN ULONG TtCount)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;

    DPRINT("USBHI_Initialize20Hub: UsbdHubDeviceHandle - %p, TtCount - %x\n",
           UsbdHubDeviceHandle,
           TtCount);

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;

    return USBPORT_Initialize20Hub(PdoExtension->FdoDevice,
                                   (PUSBPORT_DEVICE_HANDLE)UsbdHubDeviceHandle,
                                   TtCount);
}

NTSTATUS
USB_BUSIFFN
USBHI_RootHubInitNotification(IN PVOID BusContext,
                              IN PVOID CallbackContext,
                              IN PRH_INIT_CALLBACK CallbackFunction)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    KIRQL OldIrql;

    DPRINT("USBHI_RootHubInitNotification\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    FdoDevice = PdoExtension->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;

    KeAcquireSpinLock(&FdoExtension->RootHubCallbackSpinLock, &OldIrql);
    PdoExtension->RootHubInitContext = CallbackContext;
    PdoExtension->RootHubInitCallback = CallbackFunction;
    KeReleaseSpinLock(&FdoExtension->RootHubCallbackSpinLock, OldIrql);

    return STATUS_SUCCESS;
}

VOID
USB_BUSIFFN
USBHI_FlushTransfers(IN PVOID BusContext,
                     OUT PUSB_DEVICE_HANDLE UsbdDeviceHandle)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;

    DPRINT("USBHI_FlushTransfers: ...\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;

    USBPORT_BadRequestFlush(PdoExtension->FdoDevice);
}

VOID
USB_BUSIFFN
USBHI_SetDeviceHandleData(IN PVOID BusContext,
                          IN PVOID DeviceHandle,
                          IN PDEVICE_OBJECT UsbDevicePdo)
{
    DPRINT1("USBHI_SetDeviceHandleData: UNIMPLEMENTED. FIXME.\n");
}

/* USB bus driver Interface functions */

VOID
USB_BUSIFFN
USBDI_GetUSBDIVersion(IN PVOID BusContext,
                      OUT PUSBD_VERSION_INFORMATION VersionInfo,
                      OUT PULONG HcdCapabilities)
{
    DPRINT1("USBDI_GetUSBDIVersion: UNIMPLEMENTED. FIXME.\n");
}

NTSTATUS
USB_BUSIFFN
USBDI_QueryBusTime(IN PVOID BusContext,
                   OUT PULONG CurrentFrame)
{
    DPRINT1("USBDI_QueryBusTime: UNIMPLEMENTED. FIXME.\n");
    return STATUS_SUCCESS;
}

NTSTATUS
USB_BUSIFFN
USBDI_SubmitIsoOutUrb(IN PVOID BusContext,
                      IN PURB Urb)
{
    DPRINT1("USBDI_SubmitIsoOutUrb: UNIMPLEMENTED. FIXME.\n");
    return STATUS_SUCCESS;
}

NTSTATUS
USB_BUSIFFN
USBDI_QueryBusInformation(IN PVOID BusContext,
                          IN ULONG Level,
                          OUT PVOID BusInfoBuffer,
                          OUT PULONG BusInfoBufferLen,
                          OUT PULONG BusInfoActualLen)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    SIZE_T Length;
    PUSB_BUS_INFORMATION_LEVEL_1 Buffer1;

    DPRINT("USBDI_QueryBusInformation: Level - %p\n", Level);

    if ((Level != 0) && (Level != 1))
    {
        DPRINT1("USBDI_QueryBusInformation: Level should be 0 or 1\n");
        return STATUS_NOT_SUPPORTED;
    }

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    FdoDevice = PdoExtension->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;

    if (Level == 0)
    {
        if (BusInfoActualLen)
            *BusInfoActualLen = sizeof(USB_BUS_INFORMATION_LEVEL_0);

        if (*BusInfoBufferLen < sizeof(USB_BUS_INFORMATION_LEVEL_0))
        {
            return STATUS_BUFFER_TOO_SMALL;
        }

        *BusInfoBufferLen = sizeof(USB_BUS_INFORMATION_LEVEL_0);

        //Buffer0 = BusInfoBuffer;
        DPRINT1("USBDI_QueryBusInformation: LEVEL_0 UNIMPLEMENTED. FIXME\n");
        //Buffer0->TotalBandwidth = USBPORT_GetTotalBandwidth();
        //Buffer0->ConsumedBandwidth = USBPORT_GetAllocatedBandwidth();

        return STATUS_SUCCESS;
    }

    if (Level == 1)
    {
        Length = sizeof(USB_BUS_INFORMATION_LEVEL_1) +
                 FdoExtension->CommonExtension.SymbolicLinkName.Length;

        if (BusInfoActualLen)
            *BusInfoActualLen = Length;

        if (*BusInfoBufferLen < Length)
        {
            return STATUS_BUFFER_TOO_SMALL;
        }

        *BusInfoBufferLen = Length;

        Buffer1 = BusInfoBuffer;
        DPRINT1("USBDI_QueryBusInformation: LEVEL_1 UNIMPLEMENTED. FIXME\n");
        //Buffer1->TotalBandwidth = USBPORT_GetTotalBandwidth();
        //Buffer1->ConsumedBandwidth = USBPORT_GetAllocatedBandwidth();
        Buffer1->ControllerNameLength = FdoExtension->CommonExtension.SymbolicLinkName.Length;

        RtlCopyMemory(&Buffer1->ControllerNameUnicodeString,
                      FdoExtension->CommonExtension.SymbolicLinkName.Buffer,
                      FdoExtension->CommonExtension.SymbolicLinkName.Length);

        return STATUS_SUCCESS;
    }

    return STATUS_SUCCESS;
}

BOOLEAN
USB_BUSIFFN
USBDI_IsDeviceHighSpeed(IN PVOID BusContext)
{
    PDEVICE_OBJECT PdoDevice;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;

    DPRINT("USBDI_IsDeviceHighSpeed: ...\n");

    PdoDevice = BusContext;
    PdoExtension = PdoDevice->DeviceExtension;
    FdoDevice = PdoExtension->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    return (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2) != 0;
}

NTSTATUS
USB_BUSIFFN
USBDI_EnumLogEntry(IN PVOID BusContext,
                   IN ULONG DriverTag,
                   IN ULONG EnumTag,
                   IN ULONG P1,
                   IN ULONG P2)
{
    DPRINT1("USBDI_EnumLogEntry: UNIMPLEMENTED. FIXME.\n");
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
USBPORT_PdoQueryInterface(IN PDEVICE_OBJECT FdoDevice,
                          IN PDEVICE_OBJECT PdoDevice,
                          IN PIRP Irp)
{
    PIO_STACK_LOCATION IoStack = IoGetCurrentIrpStackLocation(Irp);
    PUSB_BUS_INTERFACE_HUB_V5 InterfaceHub;
    PUSB_BUS_INTERFACE_USBDI_V2 InterfaceDI;
    UNICODE_STRING GuidBuffer;
    NTSTATUS Status;

    DPRINT("USBPORT_PdoQueryInterface: ...\n");

    if (IsEqualGUIDAligned(IoStack->Parameters.QueryInterface.InterfaceType,
                           &USB_BUS_INTERFACE_HUB_GUID))
    {
        /* Get request parameters */
        InterfaceHub = (PUSB_BUS_INTERFACE_HUB_V5)IoStack->Parameters.QueryInterface.Interface;
        InterfaceHub->Version = IoStack->Parameters.QueryInterface.Version;

        /* Check version */
        if (IoStack->Parameters.QueryInterface.Version >= 6)
        {
            DPRINT1("USB_BUS_INTERFACE_HUB_GUID version %x not supported!\n",
                    IoStack->Parameters.QueryInterface.Version);

            return Irp->IoStatus.Status; // Version not supported
        }

        /* Interface version 0 */
        InterfaceHub->Size = IoStack->Parameters.QueryInterface.Size;
        InterfaceHub->BusContext = PdoDevice;

        InterfaceHub->InterfaceReference = USBI_InterfaceReference;
        InterfaceHub->InterfaceDereference = USBI_InterfaceDereference;

        /* Interface version 1 */
        if (IoStack->Parameters.QueryInterface.Version >= 1)
        {
            InterfaceHub->CreateUsbDevice = USBHI_CreateUsbDevice;
            InterfaceHub->InitializeUsbDevice = USBHI_InitializeUsbDevice;
            InterfaceHub->GetUsbDescriptors = USBHI_GetUsbDescriptors;
            InterfaceHub->RemoveUsbDevice = USBHI_RemoveUsbDevice;
            InterfaceHub->RestoreUsbDevice = USBHI_RestoreUsbDevice;
            InterfaceHub->QueryDeviceInformation = USBHI_QueryDeviceInformation;
        }

        /* Interface version 2 */
        if (IoStack->Parameters.QueryInterface.Version >= 2)
        {
            InterfaceHub->GetControllerInformation = USBHI_GetControllerInformation;
            InterfaceHub->ControllerSelectiveSuspend = USBHI_ControllerSelectiveSuspend;
            InterfaceHub->GetExtendedHubInformation = USBHI_GetExtendedHubInformation;
            InterfaceHub->GetRootHubSymbolicName = USBHI_GetRootHubSymbolicName;
            InterfaceHub->GetDeviceBusContext = USBHI_GetDeviceBusContext;
            InterfaceHub->Initialize20Hub = USBHI_Initialize20Hub;
        }

        /* Interface version 3 */
        if (IoStack->Parameters.QueryInterface.Version >= 3)
            InterfaceHub->RootHubInitNotification = USBHI_RootHubInitNotification;

        /* Interface version 4 */
        if (IoStack->Parameters.QueryInterface.Version >= 4)
            InterfaceHub->FlushTransfers = USBHI_FlushTransfers;

        /* Interface version 5 */
        if (IoStack->Parameters.QueryInterface.Version >= 5)
            InterfaceHub->SetDeviceHandleData = USBHI_SetDeviceHandleData;

        /* Request completed */
        return STATUS_SUCCESS;
    }
    else if (IsEqualGUIDAligned(IoStack->Parameters.QueryInterface.InterfaceType,
                                &USB_BUS_INTERFACE_USBDI_GUID))
    {
        /* Get request parameters */
        InterfaceDI = (PUSB_BUS_INTERFACE_USBDI_V2)IoStack->Parameters.QueryInterface.Interface;
        InterfaceDI->Version = IoStack->Parameters.QueryInterface.Version;

        /* Check version */
        if (IoStack->Parameters.QueryInterface.Version >= 3)
        {
            DPRINT1("USB_BUS_INTERFACE_USBDI_GUID version %x not supported!\n",
                    IoStack->Parameters.QueryInterface.Version);

            return Irp->IoStatus.Status; // Version not supported
        }

        /* Interface version 0 */
        InterfaceDI->Size = IoStack->Parameters.QueryInterface.Size;
        InterfaceDI->BusContext = PdoDevice;
        InterfaceDI->InterfaceReference = USBI_InterfaceReference;
        InterfaceDI->InterfaceDereference = USBI_InterfaceDereference;
        InterfaceDI->GetUSBDIVersion = USBDI_GetUSBDIVersion;
        InterfaceDI->QueryBusTime = USBDI_QueryBusTime;
        InterfaceDI->SubmitIsoOutUrb = USBDI_SubmitIsoOutUrb;
        InterfaceDI->QueryBusInformation = USBDI_QueryBusInformation;

        /* Interface version 1 */
        if (IoStack->Parameters.QueryInterface.Version >= 1)
            InterfaceDI->IsDeviceHighSpeed = USBDI_IsDeviceHighSpeed;

        /* Interface version 2 */
        if (IoStack->Parameters.QueryInterface.Version >= 2)
            InterfaceDI->EnumLogEntry = USBDI_EnumLogEntry;

        return STATUS_SUCCESS;
    }
    else
    {
        /* Convert GUID to string */
        Status = RtlStringFromGUID(IoStack->Parameters.QueryInterface.InterfaceType,
                                   &GuidBuffer);

        if (NT_SUCCESS(Status))
        {
            /* Print interface */
            DPRINT1("HandleQueryInterface UNKNOWN INTERFACE GUID: %wZ Version %x\n",
                    &GuidBuffer,
                    IoStack->Parameters.QueryInterface.Version);

            RtlFreeUnicodeString(&GuidBuffer); // Free GUID buffer
        }
    }

    return Irp->IoStatus.Status;
}
