/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    USBHUBF.C

Abstract:

Environment:

    kernel mode only

Notes:


Revision History:

    

--*/


#include <wdm.h>
#include "stdarg.h"
#include "stdio.h"

#include "dbci.h"   
#include "dbclass.h"
#include "dbfilter.h"   

#include "usbioctl.h"

//
// Registry keys
//

#define DBCLASS_HUB_IS_ACPI_DBC     0x00000001
#define DBCLASS_HUB_IS_USB_DBC      0x00000002

extern LONG DBCLASS_AcpiDBCHubParentPort;


NTSTATUS
DBCLASS_UsbhubQBusRelationsComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )

/*++

Routine Description:

Arguments:

    DeviceObject - a pointer to the device object

    Irp - a pointer to the irp

    Context - NULL ptr

Return Value:

    STATUS_SUCCESS

--*/

{
    PDEVICE_RELATIONS deviceRelations;
    ULONG i;
    PDEVICE_OBJECT busFilterMdo = Context;
    PDEVICE_EXTENSION deviceExtension;
    PDEVICE_OBJECT mdoUSB;
    PDBC_CONTEXT dbcContext;
    
    deviceExtension = busFilterMdo->DeviceExtension;
    deviceRelations = (PDEVICE_RELATIONS) Irp->IoStatus.Information;

    LOGENTRY(LOG_MISC, 'UQBR', busFilterMdo, 0, deviceRelations);

    if (deviceRelations == NULL) {
         LOGENTRY(LOG_MISC, 'UQBn', busFilterMdo, 0, deviceRelations);
         return STATUS_SUCCESS;
    }

    // try to find the DBC controller associated with this hub
    // 
    // Since the filter is loaded for every hub we need to see 
    // if this hub is part of a DBC subsystem

    
    dbcContext = deviceExtension->DbcContext;

    if (dbcContext == NULL) {
        DBCLASS_KdPrint((1, "'>QBR USB,  HUB NOT DBC\n"));
        // no context means the hub is not part of DBC
        LOGENTRY(LOG_MISC, 'hQBi', 0, 0, 0);    
        return STATUS_SUCCESS;
    }

    for (i=0; i< deviceRelations->Count; i++) {

        DBCLASS_KdPrint((1, "'>QBR USB PDO[%d] %x\n", i, 
            deviceRelations->Objects[i]));
            
        LOGENTRY(LOG_MISC, 'QBRd', deviceRelations->Objects[i], i, 0);

        // hub is returning a PDO, see if we know 
        // about it
        
        mdoUSB = DBCLASS_FindDevicePdo(deviceRelations->Objects[i]);

        if (mdoUSB) {
            // we know about this one,
            // see if we can link it to a controller
            PDEVICE_EXTENSION mdoUSBDeviceExtension;
             
            mdoUSBDeviceExtension = mdoUSB->DeviceExtension;
            mdoUSBDeviceExtension->DbcContext = dbcContext;                                           
            
        } else {
            PDEVICE_OBJECT deviceFilterObject;
            NTSTATUS ntStatus;

            // don't know about it,
            // create an MDO for this device

            ntStatus = DBCLASS_CreateDeviceFilterObject(
                deviceExtension->DriverObject,
                &deviceFilterObject,
                deviceRelations->Objects[i],
                dbcContext,
                DB_FDO_USB_DEVICE);

            DBCLASS_KdPrint((1, "'>>QBR attaching to USB PDO[%d] %x\n", i, 
                deviceRelations->Objects[i]));                    
            DBCLASS_KdPrint((1, "'(dbfilter)(bus)(USB) Create DO %x for USB PDO\n", deviceFilterObject));   

        }
    }

    return STATUS_SUCCESS;
}


NTSTATUS
DBCLASS_UsbhubBusFilterDispatch(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PBOOLEAN Handled
    )
/*++

Routine Description:

   This is a call to the root hub PDO
    
Arguments:

    DeviceObject - Device bay Filter FDO 

Return Value:

    NTSTATUS

--*/
{
    PIO_STACK_LOCATION irpStack;
    NTSTATUS ntStatus;
    PDEVICE_EXTENSION deviceExtension;

    deviceExtension = DeviceObject->DeviceExtension;
    ntStatus = Irp->IoStatus.Status;
    *Handled = FALSE;
    irpStack = IoGetCurrentIrpStackLocation (Irp);

    LOGENTRY(LOG_MISC, 'HBf>', 0, DeviceObject, Irp);
    DBCLASS_ASSERT(deviceExtension->FdoType == DB_FDO_USBHUB_BUS);
    
    DBCLASS_KdPrint((2, "'(dbfilter)(bus)(USB)IRP_MJ_ (%08X)  IRP_MN_ (%08X)\n",
        irpStack->MajorFunction, irpStack->MinorFunction));  

    switch (irpStack->MajorFunction) {

    case IRP_MJ_PNP:
        switch (irpStack->MinorFunction) {    
        case IRP_MN_START_DEVICE:            
            DBCLASS_KdPrint((1, "'(dbfilter)(bus)(USB)IRP_MN_START_DEVICE\n"));    
            break;
            
        case IRP_MN_STOP_DEVICE:            
            DBCLASS_KdPrint((1, "'(dbfilter)(bus)(USB)IRP_MN_STOP_DEVICE\n"));  
            break; 
            
        case IRP_MN_REMOVE_DEVICE:            
            DBCLASS_KdPrint((1, "'(dbfilter)(bus)(USB)IRP_MN_REMOVE_DEVICE\n"));    

            // detach from the usbhub FDO and delete our
            // MDO

            DBCLASS_RemoveBusFilterMDOFromList(DeviceObject);

            IoDetachDevice(deviceExtension->TopOfStackDeviceObject);

            IoDeleteDevice (DeviceObject);           
            DBCLASS_KdPrint((1, "'REMOVE DB Filter on USB HUB\n"));    
            
            break; 
            
        case IRP_MN_QUERY_DEVICE_RELATIONS:            
            DBCLASS_KdPrint((1, "'(dbfilter)(bus)(USB)IRP_MN_QUERY_DEVICE_RELATIONS\n"));    

//#if DBG
//            DBCLASS_Get1394BayPortMapping(deviceExtension->DbcContext);
//#endif

            //
            // do the check for USB hubs that are part of a DBC
            // 

            //
            // Ask the hub if it has a DBC hanging on it
            //
            if (deviceExtension->DbcContext == NULL &&
                DBCLASS_IsHubPartOfUSB_DBC(DeviceObject)) {
                
                deviceExtension->DbcContext = 
                    DBCLASS_FindControllerUSB(deviceExtension->DriverObject,
                                              DeviceObject,
                                              deviceExtension->PhysicalDeviceObject);
            } 

            *Handled = TRUE;
            
            if (irpStack->Parameters.QueryDeviceRelations.Type == BusRelations) {

                DBCLASS_KdPrint((1,"'>>QBR USB BUS\n"));
                IoCopyCurrentIrpStackLocationToNext(Irp);

                // Set up a completion routine to handle marking the IRP.
                IoSetCompletionRoutine(Irp,
                                       DBCLASS_UsbhubQBusRelationsComplete,
                                       DeviceObject,
                                       TRUE,
                                       TRUE,
                                       TRUE);
            } else {
                IoSkipCurrentIrpStackLocation(Irp)
            }

            // Now Pass down the IRP

            ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject, Irp);

            break;            
        } /* irpStack->MinorFunction */
        break;
    } /* irpStack->MajorFunction */      

    LOGENTRY(LOG_MISC, 'HBf<', 0, DeviceObject, 0);
    
    return ntStatus;
}


ULONG
DBCLASS_IsHubPartOf_DBC(
    PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    This call reads a registry key that tells us if this usb hub is part 
    of a device bay controller
    
Arguments:

    DeviceObject - Device bay Filter FDO 

Return Value:

    NTSTATUS

--*/
{
    ULONG flags = 0;
    NTSTATUS ntStatus;
    PDEVICE_EXTENSION deviceExtension;

    PAGED_CODE();
    
    deviceExtension = DeviceObject->DeviceExtension;

    ntStatus = DBCLASS_GetRegistryKeyValueForPdo(
                                           deviceExtension->PhysicalDeviceObject,
                                           FALSE,
                                           IS_DEVICE_BAY_KEY,
                                           sizeof(IS_DEVICE_BAY_KEY),
                                           &flags,
                                           sizeof(flags));

    DBCLASS_KdPrint((2, "'GetRegistryKeyValueForPdo ntStatus = %x flags = %x\n",
        ntStatus, flags));
    
    return flags;
}


BOOLEAN
DBCLASS_IsHubPartOfACPI_DBC(
    PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    This is a call to a usb hub PDO 
    
Arguments:

    DeviceObject - Device bay Filter FDO 

Return Value:

    NTSTATUS

--*/
{
    BOOLEAN isDB;
    ULONG flags;

    // see if it is an acpiDBC, if so mark it in 
    // the registry
    if (DBCLASS_AcpiDBCHubParentPort != -1) {
        PDEVICE_EXTENSION deviceExtension;

        deviceExtension = DeviceObject->DeviceExtension;                
        
        DBCLASS_CheckForAcpiDeviceBayHubs(
                            deviceExtension->PhysicalDeviceObject,
                            (ULONG) DBCLASS_AcpiDBCHubParentPort);
    }      

    flags = DBCLASS_IsHubPartOf_DBC(DeviceObject);

    isDB = (BOOLEAN) flags & DBCLASS_HUB_IS_ACPI_DBC; 

#ifdef DBG
    if (isDB) {
        DBCLASS_KdPrint((1, "'*** USBHUB for ACPI DBC Found\n"));
        BRK_ON_TRAP();
    }     
#endif    

    return isDB;
}


BOOLEAN
DBCLASS_IsHubPartOfUSB_DBC(
    PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    This is a call to a usb hub PDO 
    
Arguments:

    DeviceObject - Device bay Filter FDO 

Return Value:

    NTSTATUS

--*/
{
    BOOLEAN isDB;
    ULONG flags;

    flags = DBCLASS_IsHubPartOf_DBC(DeviceObject);

    isDB = (BOOLEAN) flags & DBCLASS_HUB_IS_USB_DBC; 

#ifdef DBG
    if (isDB) {
        DBCLASS_KdPrint((1, "'USBHUB for USB DBC Found!\n"));
        BRK_ON_TRAP();
    }     
#endif    

    return isDB;
}


NTSTATUS
DBCLASS_GetHubDBCGuid(
    PDEVICE_OBJECT DeviceObject,
    PUCHAR DbcGuid
    )
/*++

Routine Description:

   This is a call to the root hub PDO
    
Arguments:

    DeviceObject - Device bay Filter FDO 

Return Value:

    NTSTATUS

--*/
{
    NTSTATUS ntStatus;
    PDEVICE_EXTENSION deviceExtension;

    PAGED_CODE();
    deviceExtension = DeviceObject->DeviceExtension;

    ntStatus = DBCLASS_GetRegistryKeyValueForPdo(
                                           deviceExtension->PhysicalDeviceObject,
                                           FALSE,
                                           DBC_GUID_KEY,
                                           sizeof(DBC_GUID_KEY),
                                           DbcGuid,
                                           8);

    DBCLASS_KdPrint((2, "'GetRegistryKeyValueForPdo ntStatus = %x \n",
        ntStatus));

#if DBG    
    DBCLASS_KdPrint((1, "'DBC GUID FOR HUB\n"));
    DBCLASS_KdPrintGuid(1, DbcGuid);
#endif    

    return ntStatus;
}


NTSTATUS 
DBCLASS_SyncGetUsbInfo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PDEVICE_OBJECT *ParentDeviceObject,
    IN PDEVICE_OBJECT *RootHubPdo,
    IN PULONG PortNumber
    )
 /* ++
  * 
  * Routine Description:
  *
  * Arguments:
  * 
  * Return Value:
  * 
  * NTSTATUS
  * 
  * -- */
{
    NTSTATUS ntStatus, status;
    PIRP irp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;
    PIO_STACK_LOCATION nextStack;

    PAGED_CODE();

    //
    // issue a synchronous request to the Hub Pdo
    //
    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildDeviceIoControlRequest( IOCTL_INTERNAL_USB_GET_PARENT_HUB_INFO,
                                         DeviceObject,
                                         NULL,
                                         0,
                                         NULL,
                                         0,
                                         TRUE,  // INTERNAL
                                         &event,
                                         &ioStatus);

    if (NULL == irp) {
        TRAP();
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    nextStack = IoGetNextIrpStackLocation(irp);
    nextStack->Parameters.Others.Argument1 = ParentDeviceObject;
    nextStack->Parameters.Others.Argument2 = PortNumber;
    nextStack->Parameters.Others.Argument4 = RootHubPdo;
     
    ntStatus = IoCallDriver(DeviceObject, irp);

    if (ntStatus == STATUS_PENDING) {

        status = KeWaitForSingleObject(&event,
                                       Suspended,
                                       KernelMode,
                                       FALSE,
                                       NULL);

    } else {
        ioStatus.Status = ntStatus;
    }

    ntStatus = ioStatus.Status;

    DBCLASS_KdPrint((0, "'>>USB-PDO-INFO((%08X))   Parent = (%08X) Port = %d status = %x\n",
        DeviceObject, *ParentDeviceObject, *PortNumber, ntStatus)); 

    return ntStatus;
}


USHORT
DBCLASS_GetBayForUSBPdo(
    PDBC_CONTEXT DbcContext,
    PDEVICE_OBJECT PdoUSB
    )
/*++

Routine Description:

    given a USB PDO figure out wich bay it is associated with
    
Arguments:

Return Value:

    zero if not a device bay PDO.

--*/
{
    USHORT bay = 0;
    NTSTATUS ntStatus;
    PDEVICE_OBJECT parent = NULL, rootHubPdo = NULL;
    ULONG portNumber = 0xFFFFFFFF;

    PAGED_CODE();

    // find out what port this PDO is in
    ntStatus = DBCLASS_SyncGetUsbInfo(PdoUSB, 
                                      &parent, 
                                      &rootHubPdo,
                                      &portNumber);

    if (NT_SUCCESS(ntStatus)) {
    
        for (bay=1; bay <=NUMBER_OF_BAYS(DbcContext); bay++) {
            DBCLASS_KdPrint((2, "'bay[%d]-> port %d, hub (%08X)\n", 
                bay, 
                DbcContext->BayInformation[bay].UsbHubPort,
                DbcContext->BayInformation[bay].UsbHubPdo));    
            
            if (DbcContext->BayInformation[bay].UsbHubPort == portNumber 
                /*&&
                DbcContext->BayInformation[bay].UsbHubPdo == parent */) {

                break;
            }
        }

    }        
        
    if (!bay || bay > NUMBER_OF_BAYS(DbcContext)) {
        bay = 0;
        
        DBCLASS_KdPrint((2, "'No bay->port mapping for USB PDO\n"));              
    } 


    return bay;
}


NTSTATUS
DBCLASS_SetupUSB_DBC(
    PDBC_CONTEXT DbcContext
    )
/*++

Routine Description:

    given a USB DbcContext, write the appropriate keys
    to the registry
    
Arguments:

Return Value:

    NTSTATUS
    
--*/
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    ULONG flags = DBCLASS_HUB_IS_USB_DBC;
    PDEVICE_OBJECT parentHubPdo = NULL, rootHubPdo = NULL;
    ULONG portNumber;

    
    
    // get the parent hub info
    
    ntStatus = DBCLASS_SyncGetUsbInfo(
        DbcContext->ControllerPdo, 
        &parentHubPdo, 
        &rootHubPdo,
        &portNumber);
        

    // set the keys
    
    if (NT_SUCCESS(ntStatus)) {
        ntStatus = DBCLASS_SetRegistryKeyValueForPdo(
                        parentHubPdo,
                        FALSE,
                        REG_DWORD,
                        IS_DEVICE_BAY_KEY,
                        sizeof(IS_DEVICE_BAY_KEY),
                        &flags,
                        sizeof(flags));    
    }                        
                    
    if (NT_SUCCESS(ntStatus)) {
        ntStatus = DBCLASS_SetRegistryKeyValueForPdo(
                        parentHubPdo,
                        FALSE,
                        REG_BINARY,
                        DBC_GUID_KEY,
                        sizeof(DBC_GUID_KEY),
                        &DbcContext->SubsystemDescriptor.guid1394Link[0],
                        8);                      
    }                        
    
    return ntStatus;        
}


NTSTATUS
DBCLASS_CheckForAcpiDeviceBayHubs(
    PDEVICE_OBJECT HubPdo,
    ULONG AcpiDBCHubParentPort
    )
/*++

Routine Description:

    Check to see if this device object
    is for a hub that is part of an ACPI DBC
    
Arguments:

    AcpiDBCHubParentPort 
        0 = acpi hub is root otherwise upstream port on root 
            hub the ACPI hub is connected to


Return Value:

    NTSTATUS
    
--*/
{
    PDEVICE_OBJECT parentHubPdo = NULL, rootHubPdo = NULL;
    ULONG portNumber = 0;
    BOOLEAN writeKeys = FALSE;
    NTSTATUS ntStatus;

    // get the root hub PDO
    ntStatus = DBCLASS_SyncGetUsbInfo(
                HubPdo, 
                &parentHubPdo, 
                &rootHubPdo,
                &portNumber);

    // failure indicates this is root                
    if (!NT_SUCCESS(ntStatus)) {
        ntStatus = STATUS_SUCCESS;
        rootHubPdo = HubPdo;
    }

    if (NT_SUCCESS(ntStatus)) {
    
        DBCLASS_KdPrint((1, "'>**Check Hub:  RHPDO = %x, parentPDO %x, port %d\n",
            rootHubPdo,
            parentHubPdo, 
            portNumber));  
            
        // is this the root hub?
        if (HubPdo == rootHubPdo) {
            // Yes
            if (AcpiDBCHubParentPort == 0) {
                // root hub is acpi hub
                writeKeys = TRUE;
            }
            
        } else {        
            // is the parent the root hub?
            if (parentHubPdo == rootHubPdo) {
                // Yes 
                if (AcpiDBCHubParentPort == portNumber) {
                    // root hub ius acpi hub
                    writeKeys = TRUE;
                }    
            }
        }
    }
    
    if (writeKeys) {
        ULONG flags;

        flags = DBCLASS_HUB_IS_ACPI_DBC;
        
        DBCLASS_SetRegistryKeyValueForPdo(
                        HubPdo,
                        FALSE,
                        REG_DWORD,
                        IS_DEVICE_BAY_KEY,
                        sizeof(IS_DEVICE_BAY_KEY),
                        &flags,
                        sizeof(flags));    
                    
    
//        DBCLASS_SetRegistryKeyValueForPdo(
//                        HubPdo,
//                        FALSE,
//                        REG_BINARY,
//                        DBC_GUID_KEY,
//                        sizeof(DBC_GUID_KEY),
//                        &DbcContext->SubsystemDescriptor.guid1394Link[0],
//                        8);                      
    }        

    return ntStatus;            
}