reactos/drivers/bluetooth/fbtusb/fbtdev.c

1199 lines
37 KiB
C
Raw Normal View History

// 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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 NTAPI 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((PVOID*)&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 NTAPI 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((PVOID*)&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 NTAPI 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 NTAPI 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 NTAPI 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;
}