// Copyright (c) 2004, Antony C. Roberts

// Use of this file is subject to the terms
// described in the LICENSE.TXT file that
// accompanies this file.
//
// Your use of this file indicates your
// acceptance of the terms described in
// LICENSE.TXT.
//
// http://www.freebt.net

#include "fbtusb.h"
#include "fbtpnp.h"
#include "fbtpwr.h"
#include "fbtdev.h"
#include "fbtwmi.h"
#include "fbtrwr.h"

#include "fbtusr.h"

// Dispatch routine for CreateHandle
NTSTATUS FreeBT_DispatchCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    ULONG                       i;
    NTSTATUS                    ntStatus;
    PFILE_OBJECT                fileObject;
    PDEVICE_EXTENSION           deviceExtension;
    PIO_STACK_LOCATION          irpStack;
    PFREEBT_PIPE_CONTEXT		pipeContext;
    PUSBD_INTERFACE_INFORMATION	interface;

    PAGED_CODE();

    FreeBT_DbgPrint(3, ("FreeBT_DispatchCreate: Entered\n"));

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    fileObject = irpStack->FileObject;
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    if (deviceExtension->DeviceState != Working)
    {
        ntStatus = STATUS_INVALID_DEVICE_STATE;
        goto FreeBT_DispatchCreate_Exit;

    }

    if (deviceExtension->UsbInterface)
    {
        interface = deviceExtension->UsbInterface;

    }

    else
    {
        FreeBT_DbgPrint(1, ("UsbInterface not found\n"));
        ntStatus = STATUS_INVALID_DEVICE_STATE;
        goto FreeBT_DispatchCreate_Exit;

    }

    if (fileObject)
    {
        fileObject->FsContext = NULL;
    }

    else
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        goto FreeBT_DispatchCreate_Exit;

    }

	if (deviceExtension->OpenHandleCount>0)
	{
		ntStatus = STATUS_ACCESS_VIOLATION;
		goto FreeBT_DispatchCreate_Exit;

	}

    // opening a device as opposed to pipe.
    ntStatus = STATUS_SUCCESS;

    InterlockedIncrement(&deviceExtension->OpenHandleCount);

    // the device is idle if it has no open handles or pending PnP Irps
    // since we just received an open handle request, cancel idle req.
    if (deviceExtension->SSEnable)
        CancelSelectSuspend(deviceExtension);

FreeBT_DispatchCreate_Exit:
    Irp->IoStatus.Status = ntStatus;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    FreeBT_DbgPrint(3, ("FreeBT_DispatchCreate: Leaving\n"));
    return ntStatus;

}

// Dispatch routine for CloseHandle
NTSTATUS FreeBT_DispatchClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS               ntStatus;
    PFILE_OBJECT           fileObject;
    PDEVICE_EXTENSION      deviceExtension;
    PIO_STACK_LOCATION     irpStack;
    PFREEBT_PIPE_CONTEXT  pipeContext;
    PUSBD_PIPE_INFORMATION pipeInformation;

    PAGED_CODE();

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    fileObject = irpStack->FileObject;
    pipeContext = NULL;
    pipeInformation = NULL;
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    FreeBT_DbgPrint(3, ("FreeBT_DispatchClose: Entered\n"));

    ntStatus = STATUS_SUCCESS;
    Irp->IoStatus.Status = ntStatus;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    InterlockedDecrement(&deviceExtension->OpenHandleCount);

    FreeBT_DbgPrint(3, ("FreeBT_DispatchClose: Leaving\n"));

    return ntStatus;

}

// Called when a HCI Send on the control pipe completes
NTSTATUS FreeBT_HCISendCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    ULONG               stageLength;
    NTSTATUS            ntStatus;

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_HCISendCompletion, status=0x%08X\n", Irp->IoStatus.Status));

	if (Irp->PendingReturned)
		IoMarkIrpPending(Irp);

    ExFreePool(Context);
	FreeBT_IoDecrement(DeviceObject->DeviceExtension);
    ntStatus = Irp->IoStatus.Status;
    Irp->IoStatus.Information = 0;

    return ntStatus;

}

// Called the DeviceIOControl handler to send an HCI command received from the user
// HCI Commands are sent on the (default) control pipe
NTSTATUS FreeBT_SendHCICommand(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID IoBuffer, IN ULONG InputBufferLength)
{
	PDEVICE_EXTENSION	deviceExtension;
    ULONG				urbFlags;
	ULONG				stageLength;
	PVOID				pBuffer;
	PURB				urb;
	NTSTATUS			ntStatus;
    PIO_STACK_LOCATION	nextStack;
	//PFBT_HCI_CMD_HEADER	pHCICommand;
	//LARGE_INTEGER		delay;

	deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
	if (!deviceExtension)
	{
		ntStatus=STATUS_INVALID_PARAMETER;
		FreeBT_DbgPrint(1, ("FBTUSB: FreeBT_SendHCICommand: Failed to get DeviceExtension\n"));
		Irp->IoStatus.Status = ntStatus;
		Irp->IoStatus.Information = 0;
		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return ntStatus;

	}

	// The user is doing a reset, reset all the pipes as well, so that any
	// old events or data are removed
	/*pHCICommand=(PFBT_HCI_CMD_HEADER)IoBuffer;
	if (pHCICommand->OpCode==FBT_HCI_CMD_RESET)
	{
		FreeBT_ResetPipe(DeviceObject, deviceExtension->EventPipe.PipeHandle);
		FreeBT_ResetPipe(DeviceObject, deviceExtension->DataInPipe.PipeHandle);
		FreeBT_ResetPipe(DeviceObject, deviceExtension->DataOutPipe.PipeHandle);
		FreeBT_ResetPipe(DeviceObject, deviceExtension->AudioInPipe.PipeHandle);
		FreeBT_ResetPipe(DeviceObject, deviceExtension->AudioOutPipe.PipeHandle);

		// Wait a second for the device to recover
		FreeBT_DbgPrint(1, ("FBTUSB: FreeBT_SendHCICommand: Sleeping\n"));
		delay.QuadPart = -10000 * 5000; // 5s
        KeWaitForSingleObject(&deviceExtension->DelayEvent,
                              Executive,
                              UserMode,
                              FALSE,
                              &delay);

		FreeBT_DbgPrint(1, ("FBTUSB: FreeBT_SendHCICommand: Finished sleeping\n"));


	}*/

	// Create the URB
    urb = (PURB)ExAllocatePool(NonPagedPool, sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST));
    if(urb == NULL)
    {
        FreeBT_DbgPrint(1, ("FBTUSB: FreeBT_SendHCICommand: Failed to alloc mem for urb\n"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
		Irp->IoStatus.Status = ntStatus;
		Irp->IoStatus.Information = 0;
		IoCompleteRequest(Irp, IO_NO_INCREMENT);
		return ntStatus;

    }

	UsbBuildVendorRequest(
		urb,
		URB_FUNCTION_CLASS_DEVICE, // This works, for CSR and Silicon Wave
		sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST),
		0,
		0,
		0,
		0,
		0,
		IoBuffer,
		NULL,
		InputBufferLength,
		NULL);

    // use the original irp as an internal device control irp
    nextStack = IoGetNextIrpStackLocation(Irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextStack->Parameters.Others.Argument1 = (PVOID) urb;
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;

    IoSetCompletionRoutine(
		Irp,
		(PIO_COMPLETION_ROUTINE)FreeBT_HCISendCompletion,
		urb,
		TRUE,
		TRUE,
		TRUE);

    // We return STATUS_PENDING; call IoMarkIrpPending.
    IoMarkIrpPending(Irp);

 	FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_SendHCICommand::"));
    FreeBT_IoIncrement(deviceExtension);

 	FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_SendHCICommand: Sending IRP %X to underlying driver\n", Irp));
    ntStatus=IoCallDriver(deviceExtension->TopOfStackDeviceObject, Irp);
    if(!NT_SUCCESS(ntStatus))
    {
        FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_SendHCICommand: IoCallDriver fails with status %X\n", ntStatus));

	 	FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_SendHCICommand::"));
		FreeBT_IoDecrement(deviceExtension);

        // If the device was surprise removed out, the pipeInformation field is invalid.
        // similarly if the request was cancelled, then we need not reset the device.
        if((ntStatus != STATUS_CANCELLED) && (ntStatus != STATUS_DEVICE_NOT_CONNECTED))
			ntStatus = FreeBT_ResetDevice(DeviceObject);

        else
            FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_SendHCICommand: ntStatus is STATUS_CANCELLED or STATUS_DEVICE_NOT_CONNECTED\n"));

		Irp->IoStatus.Status = ntStatus;
		Irp->IoStatus.Information = 0;
		IoCompleteRequest(Irp, IO_NO_INCREMENT);

		return ntStatus;

    }

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_SendHCICommand: Completed successfully\n"));

    return STATUS_PENDING;

}

// Called when a HCI Get on the event pipe completes
NTSTATUS FreeBT_HCIEventCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
{
    ULONG               stageLength;
    NTSTATUS            ntStatus;
    PIO_STACK_LOCATION  nextStack;
    PURB				urb;

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_HCIEventCompletion, status=0x%08X\n", Irp->IoStatus.Status));

	if (Irp->PendingReturned)
		IoMarkIrpPending(Irp);

    // initialize variables
	urb=(PURB)Context;
    ntStatus = Irp->IoStatus.Status;
    Irp->IoStatus.Information = urb->UrbBulkOrInterruptTransfer.TransferBufferLength;
    nextStack = IoGetNextIrpStackLocation(Irp);

    ExFreePool(Context);
	FreeBT_IoDecrement(DeviceObject->DeviceExtension);

    return ntStatus;

}

// Called from the DeviceIOControl handler to wait for an event on the interrupt pipe
NTSTATUS FreeBT_GetHCIEvent(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID IoBuffer, IN ULONG InputBufferLength)
{
	PDEVICE_EXTENSION	deviceExtension;
	PURB				urb;
	NTSTATUS			ntStatus;
    PIO_STACK_LOCATION	nextStack;

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent: Entered\n"));

    urb = NULL;
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    urb = (PURB)ExAllocatePool(NonPagedPool, sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER));
    if (urb==NULL)
    {
        FreeBT_DbgPrint(1, ("FBTUSB: FreeBT_GetHCIEvent: Failed to alloc mem for urb\n"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto FreeBT_GetHCIEvent_Exit;

    }

    UsbBuildInterruptOrBulkTransferRequest(
                            urb,
                            sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
                            deviceExtension->EventPipe.PipeHandle,
                            IoBuffer,
                            NULL,
                            InputBufferLength,
                            USBD_SHORT_TRANSFER_OK|USBD_TRANSFER_DIRECTION_IN,
                            NULL);

    // use the original irp as an internal device control irp, which we send down to the
    // USB class driver in order to get our request out on the wire
    nextStack = IoGetNextIrpStackLocation(Irp);
    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextStack->Parameters.Others.Argument1 = (PVOID) urb;
    nextStack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;

    IoSetCompletionRoutine(
		Irp,
		(PIO_COMPLETION_ROUTINE)FreeBT_HCIEventCompletion,
		urb,
		TRUE,
		TRUE,
		TRUE);

    // We return STATUS_PENDING; call IoMarkIrpPending.
    IoMarkIrpPending(Irp);

	FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent::"));
    FreeBT_IoIncrement(deviceExtension);

    ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject, Irp);
    if (!NT_SUCCESS(ntStatus))
    {
        FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent: IoCallDriver fails with status %X\n", ntStatus));

		FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent::"));
		FreeBT_IoDecrement(deviceExtension);

        // If the device was surprise removed out, the pipeInformation field is invalid.
        // similarly if the request was cancelled, then we need not reset the pipe.
        if((ntStatus != STATUS_CANCELLED) && (ntStatus != STATUS_DEVICE_NOT_CONNECTED))
        {
            ntStatus = FreeBT_ResetPipe(DeviceObject, deviceExtension->EventPipe.PipeHandle);
            if(!NT_SUCCESS(ntStatus))
            {
                FreeBT_DbgPrint(1, ("FreeBT_ResetPipe failed\n"));
                ntStatus = FreeBT_ResetDevice(DeviceObject);

            }

        }

        else
        {
            FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent: ntStatus is STATUS_CANCELLED or STATUS_DEVICE_NOT_CONNECTED\n"));

        }

        goto FreeBT_GetHCIEvent_Exit;

    }

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent: Leaving\n"));

    // Return STATUS_PENDING, when the lower driver completes the request,
    // the FreeBT_HCIEventCompletion completion routine.
    return STATUS_PENDING;

FreeBT_GetHCIEvent_Exit:
    Irp->IoStatus.Status=ntStatus;
    Irp->IoStatus.Information=0;

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_GetHCIEvent: Failure (0x%08x), completing IRP\n", ntStatus));
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return ntStatus;

}

// DeviceIOControl dispatch
NTSTATUS FreeBT_DispatchDevCtrl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    ULONG              code;
    PVOID              ioBuffer;
    ULONG              inputBufferLength;
    ULONG              outputBufferLength;
    ULONG              info;
    NTSTATUS           ntStatus;
    PDEVICE_EXTENSION  deviceExtension;
    PIO_STACK_LOCATION irpStack;

    info = 0;
    irpStack = IoGetCurrentIrpStackLocation(Irp);
    code = irpStack->Parameters.DeviceIoControl.IoControlCode;
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    ioBuffer           = Irp->AssociatedIrp.SystemBuffer;
    inputBufferLength  = irpStack->Parameters.DeviceIoControl.InputBufferLength;
    outputBufferLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength;

    if (deviceExtension->DeviceState != Working)
    {
        FreeBT_DbgPrint(1, ("FBTUSB: Invalid device state\n"));
		ntStatus = STATUS_INVALID_DEVICE_STATE;
		goto FreeBT_DispatchDevCtrlExit;

	}

    FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_DispatchDevCtrl::"));

    // Make sure that any selective suspend request has been completed.
    if (deviceExtension->SSEnable)
    {
	    FreeBT_DbgPrint(3, ("Waiting on the IdleReqPendEvent\n"));
        KeWaitForSingleObject(&deviceExtension->NoIdleReqPendEvent,
                              Executive,
                              KernelMode,
                              FALSE,
                              NULL);

    }

    switch(code)
    {
	case IOCTL_FREEBT_HCI_SEND_CMD:
		FreeBT_DbgPrint(3, ("FBTUSB: IOCTL_FREEBT_HCI_SEND_CMD received\n"));
		if (inputBufferLength<FBT_HCI_CMD_MIN_SIZE)
		{
			ntStatus = STATUS_BUFFER_TOO_SMALL;
			FreeBT_DbgPrint(3, ("FBTUSB: IOCTL_FREEBT_HCI_SEND_CMD: Buffer too small\n"));
			goto FreeBT_DispatchDevCtrlExit;

		}

		if (inputBufferLength>FBT_HCI_CMD_MAX_SIZE)
		{
			ntStatus = STATUS_INVALID_BUFFER_SIZE;
			FreeBT_DbgPrint(3, ("FBTUSB: IOCTL_FREEBT_HCI_SEND_CMD: Buffer too long\n"));
			goto FreeBT_DispatchDevCtrlExit;

		}

		return FreeBT_SendHCICommand(DeviceObject, Irp, ioBuffer, inputBufferLength);
		break;

	case IOCTL_FREEBT_HCI_GET_EVENT:
		FreeBT_DbgPrint(3, ("FBTUSB: IOCTL_FREEBT_HCI_GET_EVENT received\n"));
		if (outputBufferLength<FBT_HCI_EVENT_MAX_SIZE)
		{
			ntStatus = STATUS_BUFFER_TOO_SMALL;
			FreeBT_DbgPrint(3, ("FBTUSB: IOCTL_FREEBT_HCI_GET_EVENT: Buffer too small\n"));
			goto FreeBT_DispatchDevCtrlExit;

		}

		return FreeBT_GetHCIEvent(DeviceObject, Irp, ioBuffer, outputBufferLength);
		break;

    default:
    	FreeBT_DbgPrint(3, ("FBTUSB: Invalid IOCTL 0x%08x received\n", code));
        ntStatus = STATUS_INVALID_DEVICE_REQUEST;
        break;

    }

FreeBT_DispatchDevCtrlExit:
	Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = ntStatus;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return ntStatus;
}

// Submit URB_FUNCTION_RESET_PIPE
NTSTATUS FreeBT_ResetPipe(IN PDEVICE_OBJECT DeviceObject, IN USBD_PIPE_HANDLE PipeHandle)
{
    PURB              urb;
    NTSTATUS          ntStatus;
    PDEVICE_EXTENSION deviceExtension;

    urb = NULL;
    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    urb = (PURB)ExAllocatePool(NonPagedPool, sizeof(struct _URB_PIPE_REQUEST));
    if (urb)
    {
        urb->UrbHeader.Length = (USHORT) sizeof(struct _URB_PIPE_REQUEST);
        urb->UrbHeader.Function = URB_FUNCTION_RESET_PIPE;
        urb->UrbPipeRequest.PipeHandle = PipeHandle;

        ntStatus = CallUSBD(DeviceObject, urb);

        ExFreePool(urb);

    }

    else
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;

    if(NT_SUCCESS(ntStatus))
    {
        FreeBT_DbgPrint(3, ("FBTUSB: FreeBT_ResetPipe - success\n"));
        ntStatus = STATUS_SUCCESS;

    }

    else
        FreeBT_DbgPrint(1, ("FBTUSB: FreeBT_ResetPipe - failed\n"));

    return ntStatus;

}

// Call FreeBT_ResetParentPort to reset the device
NTSTATUS FreeBT_ResetDevice(IN PDEVICE_OBJECT DeviceObject)
{
    NTSTATUS ntStatus;
    ULONG    portStatus;

    FreeBT_DbgPrint(3, ("FreeBT_ResetDevice: Entered\n"));

    ntStatus = FreeBT_GetPortStatus(DeviceObject, &portStatus);

    if ( (NT_SUCCESS(ntStatus)) && (!(portStatus & USBD_PORT_ENABLED)) && (portStatus & USBD_PORT_CONNECTED))
        ntStatus=FreeBT_ResetParentPort(DeviceObject);

    FreeBT_DbgPrint(3, ("FreeBT_ResetDevice: Leaving\n"));

    return ntStatus;

}

// Read port status from the lower driver (USB class driver)
NTSTATUS FreeBT_GetPortStatus(IN PDEVICE_OBJECT DeviceObject, IN OUT PULONG PortStatus)
{
    NTSTATUS           ntStatus;
    KEVENT             event;
    PIRP               irp;
    IO_STATUS_BLOCK    ioStatus;
    PIO_STACK_LOCATION nextStack;
    PDEVICE_EXTENSION  deviceExtension;

    FreeBT_DbgPrint(3, ("FreeBT_GetPortStatus: Entered\n"));

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    *PortStatus = 0;

    KeInitializeEvent(&event, NotificationEvent, FALSE);
    irp = IoBuildDeviceIoControlRequest(
                    IOCTL_INTERNAL_USB_GET_PORT_STATUS,
                    deviceExtension->TopOfStackDeviceObject,
                    NULL,
                    0,
                    NULL,
                    0,
                    TRUE,
                    &event,
                    &ioStatus);

    if (NULL == irp)
    {
        FreeBT_DbgPrint(1, ("memory alloc for irp failed\n"));
        return STATUS_INSUFFICIENT_RESOURCES;

    }

    nextStack = IoGetNextIrpStackLocation(irp);
    ASSERT(nextStack != NULL);
    nextStack->Parameters.Others.Argument1 = PortStatus;

    ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject, irp);
    if (STATUS_PENDING==ntStatus)
        KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

    else
        ioStatus.Status = ntStatus;

    ntStatus = ioStatus.Status;
    FreeBT_DbgPrint(3, ("FreeBT_GetPortStatus: Leaving\n"));

    return ntStatus;

}

// Sends an IOCTL_INTERNAL_USB_RESET_PORT via the lower driver
NTSTATUS FreeBT_ResetParentPort(IN PDEVICE_OBJECT DeviceObject)
{
    NTSTATUS           ntStatus;
    KEVENT             event;
    PIRP               irp;
    IO_STATUS_BLOCK    ioStatus;
    PIO_STACK_LOCATION nextStack;
    PDEVICE_EXTENSION  deviceExtension;

    FreeBT_DbgPrint(3, ("FreeBT_ResetParentPort: Entered\n"));

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    KeInitializeEvent(&event, NotificationEvent, FALSE);
    irp = IoBuildDeviceIoControlRequest(
                    IOCTL_INTERNAL_USB_RESET_PORT,
                    deviceExtension->TopOfStackDeviceObject,
                    NULL,
                    0,
                    NULL,
                    0,
                    TRUE,
                    &event,
                    &ioStatus);

    if (NULL == irp)
    {
        FreeBT_DbgPrint(1, ("memory alloc for irp failed\n"));
        return STATUS_INSUFFICIENT_RESOURCES;

    }

    nextStack = IoGetNextIrpStackLocation(irp);
    ASSERT(nextStack != NULL);

    ntStatus = IoCallDriver(deviceExtension->TopOfStackDeviceObject, irp);
    if(STATUS_PENDING == ntStatus)
        KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

    else
        ioStatus.Status = ntStatus;


    ntStatus = ioStatus.Status;

    FreeBT_DbgPrint(3, ("FreeBT_ResetParentPort: Leaving\n"));

    return ntStatus;

}

// Send an idle request to the lower driver
NTSTATUS SubmitIdleRequestIrp(IN PDEVICE_EXTENSION DeviceExtension)
{
    PIRP                    irp;
    NTSTATUS                ntStatus;
    KIRQL                   oldIrql;
    PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;
    PIO_STACK_LOCATION      nextStack;

    FreeBT_DbgPrint(3, ("SubmitIdleRequest: Entered\n"));

    irp = NULL;
    idleCallbackInfo = NULL;

    ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

    if(PowerDeviceD0 != DeviceExtension->DevPower) {

        ntStatus = STATUS_POWER_STATE_INVALID;

        goto SubmitIdleRequestIrp_Exit;
    }

    KeAcquireSpinLock(&DeviceExtension->IdleReqStateLock, &oldIrql);

    if(InterlockedExchange(&DeviceExtension->IdleReqPend, 1)) {

        FreeBT_DbgPrint(1, ("Idle request pending..\n"));

        KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

        ntStatus = STATUS_DEVICE_BUSY;

        goto SubmitIdleRequestIrp_Exit;
    }

    //
    // clear the NoIdleReqPendEvent because we are about
    // to submit an idle request. Since we are so early
    // to clear this event, make sure that if we fail this
    // request we set back the event.
    //
    KeClearEvent(&DeviceExtension->NoIdleReqPendEvent);

    idleCallbackInfo = (PUSB_IDLE_CALLBACK_INFO)ExAllocatePool(NonPagedPool, sizeof(struct _USB_IDLE_CALLBACK_INFO));

    if(idleCallbackInfo) {

        idleCallbackInfo->IdleCallback = (USB_IDLE_CALLBACK)IdleNotificationCallback;

        idleCallbackInfo->IdleContext = (PVOID)DeviceExtension;

        ASSERT(DeviceExtension->IdleCallbackInfo == NULL);

        DeviceExtension->IdleCallbackInfo = idleCallbackInfo;

        //
        // we use IoAllocateIrp to create an irp to selectively suspend the
        // device. This irp lies pending with the hub driver. When appropriate
        // the hub driver will invoked callback, where we power down. The completion
        // routine is invoked when we power back.
        //
        irp = IoAllocateIrp(DeviceExtension->TopOfStackDeviceObject->StackSize,
                            FALSE);

        if(irp == NULL) {

            FreeBT_DbgPrint(1, ("cannot build idle request irp\n"));

            KeSetEvent(&DeviceExtension->NoIdleReqPendEvent,
                       IO_NO_INCREMENT,
                       FALSE);

            InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

            KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

            ExFreePool(idleCallbackInfo);

            ntStatus = STATUS_INSUFFICIENT_RESOURCES;

            goto SubmitIdleRequestIrp_Exit;
        }

        nextStack = IoGetNextIrpStackLocation(irp);

        nextStack->MajorFunction =
                    IRP_MJ_INTERNAL_DEVICE_CONTROL;

        nextStack->Parameters.DeviceIoControl.IoControlCode =
                    IOCTL_INTERNAL_USB_SUBMIT_IDLE_NOTIFICATION;

        nextStack->Parameters.DeviceIoControl.Type3InputBuffer =
                    idleCallbackInfo;

        nextStack->Parameters.DeviceIoControl.InputBufferLength =
                    sizeof(struct _USB_IDLE_CALLBACK_INFO);


        IoSetCompletionRoutine(irp,
                               (PIO_COMPLETION_ROUTINE)IdleNotificationRequestComplete,
                               DeviceExtension,
                               TRUE,
                               TRUE,
                               TRUE);

        DeviceExtension->PendingIdleIrp = irp;

        //
        // we initialize the count to 2.
        // The reason is, if the CancelSelectSuspend routine manages
        // to grab the irp from the device extension, then the last of the
        // CancelSelectSuspend routine/IdleNotificationRequestComplete routine
        // to execute will free this irp. We need to have this schema so that
        // 1. completion routine does not attempt to touch the irp freed by
        //    CancelSelectSuspend routine.
        // 2. CancelSelectSuspend routine doesnt wait for ever for the completion
        //    routine to complete!
        //
        DeviceExtension->FreeIdleIrpCount = 2;

        KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

        //
        // check if the device is idle.
        // A check here ensures that a race condition did not
        // completely reverse the call sequence of SubmitIdleRequestIrp
        // and CancelSelectiveSuspend
        //

        if(!CanDeviceSuspend(DeviceExtension) ||
           PowerDeviceD0 != DeviceExtension->DevPower) {

            //
            // IRPs created using IoBuildDeviceIoControlRequest should be
            // completed by calling IoCompleteRequest and not merely
            // deallocated.
            //

            FreeBT_DbgPrint(1, ("Device is not idle\n"));

            KeAcquireSpinLock(&DeviceExtension->IdleReqStateLock, &oldIrql);

            DeviceExtension->IdleCallbackInfo = NULL;

            DeviceExtension->PendingIdleIrp = NULL;

            KeSetEvent(&DeviceExtension->NoIdleReqPendEvent,
                       IO_NO_INCREMENT,
                       FALSE);

            InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

            KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

            if(idleCallbackInfo) {

                ExFreePool(idleCallbackInfo);
            }

            //
            // it is still safe to touch the local variable "irp" here.
            // the irp has not been passed down the stack, the irp has
            // no cancellation routine. The worse position is that the
            // CancelSelectSuspend has run after we released the spin
            // lock above. It is still essential to free the irp.
            //
            if(irp) {

                IoFreeIrp(irp);
            }

            ntStatus = STATUS_UNSUCCESSFUL;

            goto SubmitIdleRequestIrp_Exit;
        }

        FreeBT_DbgPrint(3, ("Cancel the timers\n"));
        //
        // Cancel the timer so that the DPCs are no longer fired.
        // Thus, we are making judicious usage of our resources.
        // we do not need DPCs because we already have an idle irp pending.
        // The timers are re-initialized in the completion routine.
        //
        KeCancelTimer(&DeviceExtension->Timer);

        ntStatus = IoCallDriver(DeviceExtension->TopOfStackDeviceObject, irp);

        if(!NT_SUCCESS(ntStatus)) {

            FreeBT_DbgPrint(1, ("IoCallDriver failed\n"));

            goto SubmitIdleRequestIrp_Exit;
        }
    }
    else {

        FreeBT_DbgPrint(1, ("Memory allocation for idleCallbackInfo failed\n"));

        KeSetEvent(&DeviceExtension->NoIdleReqPendEvent,
                   IO_NO_INCREMENT,
                   FALSE);

        InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

        KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }

SubmitIdleRequestIrp_Exit:

    FreeBT_DbgPrint(3, ("SubmitIdleRequest: Leaving\n"));

    return ntStatus;
}


VOID IdleNotificationCallback(IN PDEVICE_EXTENSION DeviceExtension)
{
    NTSTATUS                ntStatus;
    POWER_STATE             powerState;
    KEVENT                  irpCompletionEvent;
    PIRP_COMPLETION_CONTEXT irpContext;

    FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationCallback: Entered\n"));

    //
    // Dont idle, if the device was just disconnected or being stopped
    // i.e. return for the following DeviceState(s)
    // NotStarted, Stopped, PendingStop, PendingRemove, SurpriseRemoved, Removed
    //

    if(DeviceExtension->DeviceState != Working) {

        return;
    }

    //
    // If there is not already a WW IRP pending, submit one now
    //
    if(DeviceExtension->WaitWakeEnable) {

        IssueWaitWake(DeviceExtension);
    }


    //
    // power down the device
    //

    irpContext = (PIRP_COMPLETION_CONTEXT)
                 ExAllocatePool(NonPagedPool,
                                sizeof(IRP_COMPLETION_CONTEXT));

    if(!irpContext) {

        FreeBT_DbgPrint(1, ("FBTUSB: IdleNotificationCallback: Failed to alloc memory for irpContext\n"));
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }
    else {

        //
        // increment the count. In the HoldIoRequestWorkerRoutine, the
        // count is decremented twice (one for the system Irp and the
        // other for the device Irp. An increment here compensates for
        // the sytem irp..The decrement corresponding to this increment
        // is in the completion function
        //

        FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationCallback::"));
        FreeBT_IoIncrement(DeviceExtension);

        powerState.DeviceState = (DEVICE_POWER_STATE) DeviceExtension->PowerDownLevel;

        KeInitializeEvent(&irpCompletionEvent, NotificationEvent, FALSE);

        irpContext->DeviceExtension = DeviceExtension;
        irpContext->Event = &irpCompletionEvent;

        ntStatus = PoRequestPowerIrp(
                          DeviceExtension->PhysicalDeviceObject,
                          IRP_MN_SET_POWER,
                          powerState,
                          (PREQUEST_POWER_COMPLETE) PoIrpCompletionFunc,
                          irpContext,
                          NULL);

        if(STATUS_PENDING == ntStatus) {

            FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationCallback::"
                           "waiting for the power irp to complete\n"));

            KeWaitForSingleObject(&irpCompletionEvent,
                                  Executive,
                                  KernelMode,
                                  FALSE,
                                  NULL);
        }
    }

    if(!NT_SUCCESS(ntStatus)) {

        if(irpContext) {

            ExFreePool(irpContext);
        }
    }

    FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationCallback: Leaving\n"));
}


NTSTATUS IdleNotificationRequestComplete(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PDEVICE_EXTENSION DeviceExtension)
{
    NTSTATUS                ntStatus;
    POWER_STATE             powerState;
    KIRQL                   oldIrql;
    LARGE_INTEGER           dueTime;
    PIRP                    idleIrp;
    PUSB_IDLE_CALLBACK_INFO idleCallbackInfo;

    FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationRequestCompete: Entered\n"));

    idleIrp = NULL;

    ntStatus = Irp->IoStatus.Status;
    if(!NT_SUCCESS(ntStatus) && ntStatus != STATUS_NOT_SUPPORTED)
    {
        FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationRequestCompete: Idle irp completes with error::"));
        switch(ntStatus)
        {
        case STATUS_INVALID_DEVICE_REQUEST:
            FreeBT_DbgPrint(3, ("STATUS_INVALID_DEVICE_REQUEST\n"));
            break;

        case STATUS_CANCELLED:
            FreeBT_DbgPrint(3, ("STATUS_CANCELLED\n"));
            break;

        case STATUS_DEVICE_BUSY:
            FreeBT_DbgPrint(3, ("STATUS_DEVICE_BUSY\n"));
            break;

        case STATUS_POWER_STATE_INVALID:
            FreeBT_DbgPrint(3, ("STATUS_POWER_STATE_INVALID\n"));
            goto IdleNotificationRequestComplete_Exit;

        default:
            FreeBT_DbgPrint(3, ("default: status = %X\n", ntStatus));
            break;

        }

        // if in error, issue a SetD0
        FreeBT_DbgPrint(3, ("FBTUSB: IdleNotificationRequestComplete::"));
        FreeBT_IoIncrement(DeviceExtension);

        powerState.DeviceState = PowerDeviceD0;
        ntStatus = PoRequestPowerIrp(
                          DeviceExtension->PhysicalDeviceObject,
                          IRP_MN_SET_POWER,
                          powerState,
                          (PREQUEST_POWER_COMPLETE) PoIrpAsyncCompletionFunc,
                          DeviceExtension,
                          NULL);

        if(!NT_SUCCESS(ntStatus))
            FreeBT_DbgPrint(1, ("PoRequestPowerIrp failed\n"));

    }

IdleNotificationRequestComplete_Exit:
    KeAcquireSpinLock(&DeviceExtension->IdleReqStateLock, &oldIrql);
    idleCallbackInfo = DeviceExtension->IdleCallbackInfo;
    DeviceExtension->IdleCallbackInfo = NULL;

    idleIrp = (PIRP) InterlockedExchangePointer(&DeviceExtension->PendingIdleIrp, NULL);
    InterlockedExchange(&DeviceExtension->IdleReqPend, 0);

    KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

    if(idleCallbackInfo)
        ExFreePool(idleCallbackInfo);

    // Since the irp was created using IoAllocateIrp,
    // the Irp needs to be freed using IoFreeIrp.
    // Also return STATUS_MORE_PROCESSING_REQUIRED so that
    // the kernel does not reference this in the near future.
    if(idleIrp)
    {
        FreeBT_DbgPrint(3, ("completion routine has a valid irp and frees it\n"));
        IoFreeIrp(Irp);
        KeSetEvent(&DeviceExtension->NoIdleReqPendEvent, IO_NO_INCREMENT, FALSE);

    }

    else
    {
        // The CancelSelectiveSuspend routine has grabbed the Irp from the device
        // extension. Now the last one to decrement the FreeIdleIrpCount should
        // free the irp.
        if (0 == InterlockedDecrement(&DeviceExtension->FreeIdleIrpCount))
        {
            FreeBT_DbgPrint(3, ("completion routine frees the irp\n"));
            IoFreeIrp(Irp);
            KeSetEvent(&DeviceExtension->NoIdleReqPendEvent, IO_NO_INCREMENT, FALSE);

        }

    }

    if(DeviceExtension->SSEnable)
    {
        FreeBT_DbgPrint(3, ("Set the timer to fire DPCs\n"));
        dueTime.QuadPart = -10000 * IDLE_INTERVAL;               // 5000 ms
        KeSetTimerEx(&DeviceExtension->Timer, dueTime, IDLE_INTERVAL, &DeviceExtension->DeferredProcCall);
        FreeBT_DbgPrint(3, ("IdleNotificationRequestCompete: Leaving\n"));

    }

    return STATUS_MORE_PROCESSING_REQUIRED;

}

VOID CancelSelectSuspend(IN PDEVICE_EXTENSION DeviceExtension)
{
    PIRP  irp;
    KIRQL oldIrql;

    FreeBT_DbgPrint(3, ("CancelSelectSuspend: Entered\n"));

    irp = NULL;

    KeAcquireSpinLock(&DeviceExtension->IdleReqStateLock, &oldIrql);

    if(!CanDeviceSuspend(DeviceExtension))
    {
        FreeBT_DbgPrint(3, ("Device is not idle\n"));
        irp = (PIRP) InterlockedExchangePointer(&DeviceExtension->PendingIdleIrp, NULL);

    }

    KeReleaseSpinLock(&DeviceExtension->IdleReqStateLock, oldIrql);

    // since we have a valid Irp ptr,
    // we can call IoCancelIrp on it,
    // without the fear of the irp
    // being freed underneath us.
    if(irp)
    {
        // This routine has the irp pointer.
        // It is safe to call IoCancelIrp because we know that
        // the compleiton routine will not free this irp unless...
        //
        //
        if(IoCancelIrp(irp))
        {
            FreeBT_DbgPrint(3, ("IoCancelIrp returns TRUE\n"));

		}

        else
        {
            FreeBT_DbgPrint(3, ("IoCancelIrp returns FALSE\n"));

		}

        // ....we decrement the FreeIdleIrpCount from 2 to 1.
        // if completion routine runs ahead of us, then this routine
        // decrements the FreeIdleIrpCount from 1 to 0 and hence shall
        // free the irp.
        if(0 == InterlockedDecrement(&DeviceExtension->FreeIdleIrpCount))
        {
            FreeBT_DbgPrint(3, ("CancelSelectSuspend frees the irp\n"));
            IoFreeIrp(irp);
            KeSetEvent(&DeviceExtension->NoIdleReqPendEvent, IO_NO_INCREMENT, FALSE);

        }

    }

    FreeBT_DbgPrint(3, ("CancelSelectSuspend: Leaving\n"));

    return;

}

VOID PoIrpCompletionFunc(IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus)
{
    PIRP_COMPLETION_CONTEXT irpContext;
    irpContext = NULL;

    FreeBT_DbgPrint(3, ("PoIrpCompletionFunc::"));

    if(Context)
        irpContext = (PIRP_COMPLETION_CONTEXT) Context;

    // all we do is set the event and decrement the count
    if(irpContext)
    {
        KeSetEvent(irpContext->Event, 0, FALSE);
    	FreeBT_IoDecrement(irpContext->DeviceExtension);
        ExFreePool(irpContext);

    }

    return;

}

VOID PoIrpAsyncCompletionFunc(IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus)
{
    PDEVICE_EXTENSION DeviceExtension = (PDEVICE_EXTENSION) Context;
    FreeBT_DbgPrint(3, ("PoIrpAsyncCompletionFunc::"));
    FreeBT_IoDecrement(DeviceExtension);

    return;

}

VOID WWIrpCompletionFunc(IN PDEVICE_OBJECT DeviceObject, IN UCHAR MinorFunction, IN POWER_STATE PowerState, IN PVOID Context, IN PIO_STATUS_BLOCK IoStatus)
{
    PDEVICE_EXTENSION DeviceExtension = (PDEVICE_EXTENSION) Context;

    FreeBT_DbgPrint(3, ("WWIrpCompletionFunc::"));
    FreeBT_IoDecrement(DeviceExtension);

    return;

}