578 lines
16 KiB
C
578 lines
16 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pnp.c
|
|
|
|
Abstract: Human Input Device (HID) minidriver for Universal Serial Bus (USB) devices
|
|
|
|
The HID USB Minidriver (HUM, Hum) provides an abstraction layer for the
|
|
HID Class so that future HID devices whic are not USB devices can be supported.
|
|
|
|
Author:
|
|
|
|
Environment:
|
|
|
|
Kernel mode
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
#include "pch.h"
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, HumPnP)
|
|
#pragma alloc_text(PAGE, HumStartDevice)
|
|
#pragma alloc_text(PAGE, HumStopDevice)
|
|
#pragma alloc_text(PAGE, HumRemoveDevice)
|
|
#pragma alloc_text(PAGE, HumAbortPendingRequests)
|
|
#endif
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumPnP
|
|
************************************************************
|
|
*
|
|
* Process PnP IRPs sent to this device.
|
|
*
|
|
*/
|
|
NTSTATUS HumPnP(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
|
|
{
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
PIO_STACK_LOCATION irpSp;
|
|
PDEVICE_EXTENSION DeviceExtension;
|
|
KEVENT event;
|
|
|
|
PAGED_CODE();
|
|
|
|
DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject);
|
|
irpSp = IoGetCurrentIrpStackLocation (Irp);
|
|
|
|
switch(irpSp->MinorFunction){
|
|
|
|
case IRP_MN_START_DEVICE:
|
|
ntStatus = HumStartDevice(DeviceObject);
|
|
break;
|
|
|
|
case IRP_MN_STOP_DEVICE:
|
|
if (DeviceExtension->DeviceState == DEVICE_STATE_RUNNING) {
|
|
ntStatus = HumStopDevice(DeviceObject);
|
|
} else {
|
|
ntStatus = STATUS_SUCCESS;
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_REMOVE_DEVICE:
|
|
return HumRemoveDevice(DeviceObject, Irp);
|
|
break;
|
|
}
|
|
|
|
if (NT_SUCCESS(ntStatus)){
|
|
/*
|
|
* Our processing has succeeded.
|
|
* So pass this IRP down to the next driver.
|
|
*/
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
IoSetCompletionRoutine(Irp,
|
|
HumPnpCompletion,
|
|
&event, // context
|
|
TRUE,
|
|
TRUE,
|
|
TRUE );
|
|
ntStatus = IoCallDriver(GET_NEXT_DEVICE_OBJECT(DeviceObject), Irp);
|
|
|
|
if (ntStatus == STATUS_PENDING) {
|
|
// wait for it...
|
|
KeWaitForSingleObject(&event,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
}
|
|
|
|
ntStatus = Irp->IoStatus.Status;
|
|
|
|
switch(irpSp->MinorFunction) {
|
|
case IRP_MN_START_DEVICE:
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
DeviceExtension->DeviceState = DEVICE_STATE_RUNNING;
|
|
|
|
ntStatus = HumInitDevice(DeviceObject);
|
|
|
|
if (!NT_SUCCESS(ntStatus)) {
|
|
DBGWARN(("HumInitDevice failed; failing IRP_MN_START_DEVICE."));
|
|
DeviceExtension->DeviceState = DEVICE_STATE_START_FAILED;
|
|
Irp->IoStatus.Status = ntStatus;
|
|
}
|
|
}
|
|
else {
|
|
DBGWARN(("Pdo failed start irp with status %x", ntStatus));
|
|
DeviceExtension->DeviceState = DEVICE_STATE_START_FAILED;
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_STOP_DEVICE:
|
|
|
|
DeviceExtension->DeviceState = DEVICE_STATE_STOPPED;
|
|
|
|
/*
|
|
* Release resources
|
|
*/
|
|
if (DeviceExtension->Interface) {
|
|
ExFreePool(DeviceExtension->Interface);
|
|
DeviceExtension->Interface = NULL;
|
|
}
|
|
if (DeviceExtension->DeviceDescriptor) {
|
|
ExFreePool(DeviceExtension->DeviceDescriptor);
|
|
DeviceExtension->DeviceDescriptor = NULL;
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_QUERY_CAPABILITIES:
|
|
/*
|
|
* The lower driver set the capabilities flags for this device.
|
|
* Since all USB devices are hot-unpluggable,
|
|
* add the SurpriseRemovalOK bit.
|
|
*/
|
|
if (NT_SUCCESS(ntStatus)){
|
|
irpSp->Parameters.DeviceCapabilities.Capabilities->SurpriseRemovalOK = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
DBGWARN(("A PnP irp is going to be failed. Status = %x.", ntStatus));
|
|
}
|
|
|
|
Irp->IoStatus.Status = ntStatus;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS HumPowerCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
ASSERT(DeviceObject);
|
|
|
|
status = Irp->IoStatus.Status;
|
|
|
|
if (Irp->PendingReturned) {
|
|
IoMarkIrpPending(Irp);
|
|
}
|
|
|
|
if (NT_SUCCESS(status)){
|
|
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
switch (irpSp->MinorFunction){
|
|
|
|
case IRP_MN_SET_POWER:
|
|
|
|
switch (irpSp->Parameters.Power.Type){
|
|
case DevicePowerState:
|
|
switch (irpSp->Parameters.Power.State.DeviceState) {
|
|
case PowerDeviceD0:
|
|
/*
|
|
* We just resumed from SUSPEND.
|
|
* Send down a SET_IDLE to prevent keyboards
|
|
* from chattering after the resume.
|
|
*/
|
|
status = HumSetIdle(DeviceObject);
|
|
/* if (!NT_SUCCESS(status)){
|
|
DBGWARN(("HumPowerCompletion: SET_IDLE failed with %xh (only matters for keyboard).", status));
|
|
}*/
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumPower
|
|
************************************************************
|
|
*
|
|
* Process Power IRPs sent to this device.
|
|
*
|
|
*/
|
|
NTSTATUS HumPower(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
IoSetCompletionRoutine(Irp, HumPowerCompletion, NULL, TRUE, TRUE, TRUE);
|
|
status = PoCallDriver(GET_NEXT_DEVICE_OBJECT(DeviceObject), Irp);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumStartDevice
|
|
************************************************************
|
|
*
|
|
* Initializes a given instance of the UTB device on the USB.
|
|
*
|
|
*/
|
|
NTSTATUS HumStartDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
PDEVICE_EXTENSION DeviceExtension;
|
|
ULONG oldDeviceState;
|
|
PAGED_CODE();
|
|
|
|
DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject);
|
|
|
|
oldDeviceState = DeviceExtension->DeviceState;
|
|
DeviceExtension->DeviceState = DEVICE_STATE_STARTING;
|
|
|
|
/*
|
|
* We may have been previously stopped, in which case the AllRequestsCompleteEvent
|
|
* is still in the signalled state. It's very important that we reset it to
|
|
* the non-signalled state so that we wait on it properly on the next stop/remove.
|
|
*/
|
|
KeResetEvent(&DeviceExtension->AllRequestsCompleteEvent);
|
|
|
|
ASSERT(oldDeviceState != DEVICE_STATE_REMOVING);
|
|
|
|
if ((oldDeviceState == DEVICE_STATE_STOPPING) ||
|
|
(oldDeviceState == DEVICE_STATE_STOPPED) ||
|
|
(oldDeviceState == DEVICE_STATE_REMOVING)){
|
|
|
|
/*
|
|
* We did an extra decrement when the device was stopped.
|
|
* Now that we're restarting, we need to bump it back to zero.
|
|
*/
|
|
NTSTATUS incStat = HumIncrementPendingRequestCount(DeviceExtension);
|
|
ASSERT(NT_SUCCESS(incStat));
|
|
ASSERT(DeviceExtension->NumPendingRequests == 0);
|
|
DBGWARN(("Got start-after-stop; re-incremented pendingRequestCount"));
|
|
}
|
|
|
|
DeviceExtension->Interface = NULL;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumInitDevice
|
|
************************************************************
|
|
*
|
|
* Get the device information and attempt to initialize a configuration
|
|
* for a device. If we cannot identify this as a valid HID device or
|
|
* configure the device, our start device function is failed.
|
|
*
|
|
* Note: This function is called from the PnP completion routine,
|
|
* so it cannot be pageable.
|
|
*/
|
|
NTSTATUS HumInitDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
NTSTATUS ntStatus;
|
|
PDEVICE_EXTENSION DeviceExtension;
|
|
PUSB_CONFIGURATION_DESCRIPTOR ConfigDesc = NULL;
|
|
ULONG DescriptorLength;
|
|
|
|
DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject);
|
|
|
|
/*
|
|
* Get the Device descriptor and store it in the device extension
|
|
*/
|
|
ntStatus = HumGetDeviceDescriptor(DeviceObject, DeviceExtension);
|
|
if (NT_SUCCESS(ntStatus)){
|
|
|
|
/*
|
|
* Get config descriptor
|
|
*/
|
|
ntStatus = HumGetConfigDescriptor(DeviceObject, &ConfigDesc, &DescriptorLength);
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
|
|
ASSERT(ConfigDesc);
|
|
|
|
#if DBG
|
|
// NOTE: This debug function is currently confused
|
|
// by power descriptors. Restore when fixed.
|
|
// DumpConfigDescriptor(ConfigDesc, DescriptorLength);
|
|
#endif
|
|
|
|
ntStatus = HumGetHidInfo(DeviceObject, ConfigDesc, DescriptorLength);
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
|
|
ntStatus = HumSelectConfiguration(DeviceObject, ConfigDesc);
|
|
if (NT_SUCCESS(ntStatus)) {
|
|
HumSetIdle(DeviceObject);
|
|
}
|
|
}
|
|
|
|
ExFreePool(ConfigDesc);
|
|
}
|
|
|
|
}
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumStopDevice
|
|
************************************************************
|
|
*
|
|
* Stops a given instance of a device on the USB.
|
|
*
|
|
*/
|
|
NTSTATUS HumStopDevice(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
PURB Urb;
|
|
ULONG Size;
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
PDEVICE_EXTENSION DeviceExtension;
|
|
|
|
PAGED_CODE();
|
|
|
|
DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject);
|
|
DeviceExtension->DeviceState = DEVICE_STATE_STOPPING;
|
|
|
|
/*
|
|
* Abort all pending IO on the device.
|
|
* We do an extra decrement here, which causes the
|
|
* NumPendingRequests to eventually go to -1, which causes
|
|
* AllRequestsCompleteEvent to get set.
|
|
* NumPendingRequests will get reset to 0 when we re-start.
|
|
*/
|
|
HumAbortPendingRequests(DeviceObject);
|
|
HumDecrementPendingRequestCount(DeviceExtension);
|
|
KeWaitForSingleObject( &DeviceExtension->AllRequestsCompleteEvent,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL );
|
|
|
|
/*
|
|
* Submit an open configuration Urb to the USB stack
|
|
* (with a NULL pointer for the configuration handle).
|
|
*/
|
|
Size = sizeof(struct _URB_SELECT_CONFIGURATION);
|
|
Urb = ExAllocatePoolWithTag(NonPagedPool, Size, HIDUSB_TAG);
|
|
if (Urb){
|
|
UsbBuildSelectConfigurationRequest(Urb, (USHORT) Size, NULL);
|
|
|
|
ntStatus = HumCallUSB(DeviceObject, Urb);
|
|
ASSERT(NT_SUCCESS(ntStatus));
|
|
|
|
ExFreePool(Urb);
|
|
}
|
|
else {
|
|
ntStatus = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
if (!NT_SUCCESS(ntStatus)){
|
|
/*
|
|
* We will not pass this IRP down,
|
|
* so our completion routine will not set the device's
|
|
* state to DEVICE_STATE_STOPPED; so set it here.
|
|
*/
|
|
ASSERT(NT_SUCCESS(ntStatus));
|
|
DeviceExtension->DeviceState = DEVICE_STATE_STOPPED;
|
|
}
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumAbortPendingRequests
|
|
************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HumAbortPendingRequests(IN PDEVICE_OBJECT DeviceObject)
|
|
{
|
|
PDEVICE_EXTENSION deviceExtension;
|
|
PURB urb;
|
|
PVOID pipeHandle;
|
|
ULONG urbSize;
|
|
NTSTATUS status;
|
|
|
|
PAGED_CODE();
|
|
|
|
deviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION( DeviceObject );
|
|
|
|
/*
|
|
* Create and send down an abort pipe request.
|
|
*/
|
|
urbSize = sizeof(struct _URB_PIPE_REQUEST);
|
|
urb = ExAllocatePoolWithTag(NonPagedPool, urbSize, HIDUSB_TAG);
|
|
if (urb){
|
|
|
|
if (deviceExtension->Interface &&
|
|
(deviceExtension->Interface->NumberOfPipes != 0)){
|
|
|
|
pipeHandle = deviceExtension->Interface->Pipes[0].PipeHandle;
|
|
if (pipeHandle) {
|
|
urb->UrbHeader.Length = (USHORT)urbSize;
|
|
urb->UrbHeader.Function = URB_FUNCTION_ABORT_PIPE;
|
|
urb->UrbPipeRequest.PipeHandle = pipeHandle;
|
|
|
|
status = HumCallUSB(DeviceObject, urb);
|
|
if (!NT_SUCCESS(status)){
|
|
DBGWARN(("URB_FUNCTION_ABORT_PIPE returned %xh in HumAbortPendingRequests", status));
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(pipeHandle);
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
}
|
|
else {
|
|
DBGERR(("No such device in HumAbortPendingRequests"));
|
|
status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
ExFreePool(urb);
|
|
}
|
|
else {
|
|
ASSERT(urb);
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumPnpCompletion
|
|
************************************************************
|
|
*
|
|
*/
|
|
NTSTATUS HumPnpCompletion( IN PDEVICE_OBJECT DeviceObject,
|
|
IN PIRP Irp,
|
|
IN PVOID Context)
|
|
{
|
|
UNREFERENCED_PARAMETER (DeviceObject);
|
|
|
|
if (Irp->PendingReturned) {
|
|
IoMarkIrpPending( Irp );
|
|
}
|
|
|
|
KeSetEvent ((PKEVENT) Context, 1, FALSE);
|
|
// No special priority
|
|
// No Wait
|
|
|
|
return STATUS_MORE_PROCESSING_REQUIRED; // Keep this IRP
|
|
}
|
|
|
|
|
|
/*
|
|
************************************************************
|
|
* HumRemoveDevice
|
|
************************************************************
|
|
*
|
|
* Removes a given instance of a device on the USB.
|
|
*
|
|
*/
|
|
NTSTATUS HumRemoveDevice(IN PDEVICE_OBJECT DeviceObject, PIRP Irp)
|
|
{
|
|
NTSTATUS ntStatus = STATUS_SUCCESS;
|
|
PDEVICE_EXTENSION DeviceExtension;
|
|
ULONG oldDeviceState;
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Get a pointer to the device extension
|
|
//
|
|
|
|
DeviceExtension = GET_MINIDRIVER_DEVICE_EXTENSION(DeviceObject);
|
|
|
|
//
|
|
// Set device state, this prevents new IOs from starting
|
|
//
|
|
|
|
oldDeviceState = DeviceExtension->DeviceState;
|
|
DeviceExtension->DeviceState = DEVICE_STATE_REMOVING;
|
|
|
|
|
|
/*
|
|
* Note: RemoveDevice does an extra decrement, so we complete
|
|
* the REMOVE IRP on the transition to -1, whether this
|
|
* happens in RemoveDevice itself or subsequently while
|
|
* RemoveDevice is waiting for this event to fire.
|
|
*/
|
|
if ((oldDeviceState == DEVICE_STATE_STOPPING) ||
|
|
(oldDeviceState == DEVICE_STATE_STOPPED)){
|
|
/*
|
|
* HumStopDevice did the extra decrement and aborted the
|
|
* pending requests.
|
|
*/
|
|
}
|
|
else {
|
|
HumDecrementPendingRequestCount(DeviceExtension);
|
|
}
|
|
|
|
//
|
|
// Cancel any outstanding IRPs if the device was running
|
|
//
|
|
if (oldDeviceState == DEVICE_STATE_RUNNING){
|
|
HumAbortPendingRequests(DeviceObject);
|
|
}
|
|
else if (oldDeviceState == DEVICE_STATE_STOPPING){
|
|
ASSERT(!(PVOID)"PnP IRPs are not synchronized! -- got REMOVE_DEVICE before STOP_DEVICE completed!");
|
|
}
|
|
|
|
KeWaitForSingleObject( &DeviceExtension->AllRequestsCompleteEvent,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL );
|
|
|
|
ASSERT(DeviceExtension->NumPendingRequests == -1);
|
|
|
|
//
|
|
// Fire and forget
|
|
//
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
ntStatus = IoCallDriver(GET_NEXT_DEVICE_OBJECT(DeviceObject), Irp);
|
|
|
|
//
|
|
// Release any resources
|
|
//
|
|
|
|
if (DeviceExtension->Interface) {
|
|
ExFreePool(DeviceExtension->Interface);
|
|
DeviceExtension->Interface = NULL;
|
|
}
|
|
|
|
if (DeviceExtension->DeviceDescriptor) {
|
|
ExFreePool(DeviceExtension->DeviceDescriptor);
|
|
DeviceExtension->DeviceDescriptor = NULL;
|
|
}
|
|
|
|
return ntStatus;
|
|
}
|
|
|
|
|