1998 lines
70 KiB
C
1998 lines
70 KiB
C
/*++
|
|
|
|
Copyright (c) 1996 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
dispatch.c
|
|
|
|
Abstract
|
|
|
|
Dispatch routines for the HID class driver.
|
|
|
|
Author:
|
|
|
|
Ervin P.
|
|
|
|
Environment:
|
|
|
|
Kernel mode only
|
|
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
#include "pch.h"
|
|
#include <poclass.h>
|
|
#include <wdmguid.h>
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
#pragma alloc_text(PAGE, HidpCallDriverSynchronous)
|
|
#pragma alloc_text(PAGE, HidpIrpMajorPnp)
|
|
#pragma alloc_text(PAGE, HidpFdoPnp)
|
|
#pragma alloc_text(PAGE, HidpPdoPnp)
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpCallDriver
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpCallDriver(IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp)
|
|
{
|
|
PHIDCLASS_DEVICE_EXTENSION hidDeviceExtension;
|
|
PHIDCLASS_DRIVER_EXTENSION hidDriverExtension;
|
|
PIO_STACK_LOCATION irpSp;
|
|
NTSTATUS status;
|
|
#if DBG
|
|
KIRQL saveIrql;
|
|
#endif
|
|
|
|
DBGASSERT((Irp->Type == IO_TYPE_IRP),
|
|
("Irp->Type != IO_TYPE_IRP, Irp->Type == %x", Irp->Type),
|
|
TRUE)
|
|
|
|
/*
|
|
* Update the IRP stack to point to the next location.
|
|
*/
|
|
Irp->CurrentLocation--;
|
|
|
|
if (Irp->CurrentLocation <= 0) {
|
|
KeBugCheckEx( NO_MORE_IRP_STACK_LOCATIONS, (ULONG_PTR) Irp, 0, 0, 0 );
|
|
}
|
|
|
|
irpSp = IoGetNextIrpStackLocation( Irp );
|
|
Irp->Tail.Overlay.CurrentStackLocation = irpSp;
|
|
|
|
//
|
|
// Save a pointer to the device object for this request so that it can
|
|
// be used later in completion.
|
|
//
|
|
|
|
irpSp->DeviceObject = DeviceObject;
|
|
|
|
//
|
|
// Get a pointer to the class extension and verify it.
|
|
//
|
|
hidDeviceExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
ASSERT(hidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG);
|
|
ASSERT(!hidDeviceExtension->isClientPdo);
|
|
|
|
//
|
|
// Ditto for the driver extension
|
|
//
|
|
|
|
hidDriverExtension = hidDeviceExtension->fdoExt.driverExt;
|
|
ASSERT( hidDriverExtension->Signature == HID_DRIVER_EXTENSION_SIG );
|
|
|
|
//
|
|
// Invoke the driver at its dispatch routine entry point.
|
|
//
|
|
|
|
#if DBG
|
|
saveIrql = KeGetCurrentIrql();
|
|
#endif
|
|
|
|
/*
|
|
* Call down to the minidriver
|
|
*/
|
|
status = hidDriverExtension->MajorFunction[irpSp->MajorFunction](DeviceObject, Irp);
|
|
|
|
#if DBG
|
|
if (saveIrql != KeGetCurrentIrql()) {
|
|
DbgPrint( "IO: HidpCallDriver( Driver ext: %x Device object: %x Irp: %x )\n",
|
|
hidDriverExtension,
|
|
DeviceObject,
|
|
Irp
|
|
);
|
|
DbgPrint( " Irql before: %x != After: %x\n", saveIrql, KeGetCurrentIrql() );
|
|
DbgBreakPoint();
|
|
}
|
|
#endif
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpSynchronousCallCompletion
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpSynchronousCallCompletion(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context)
|
|
{
|
|
PKEVENT event = Context;
|
|
|
|
DBG_COMMON_ENTRY()
|
|
|
|
KeSetEvent(event, 0, FALSE);
|
|
|
|
DBG_COMMON_EXIT()
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpCallDriverSynchronous
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpCallDriverSynchronous(IN PDEVICE_OBJECT DeviceObject, IN OUT PIRP Irp)
|
|
{
|
|
KEVENT event;
|
|
NTSTATUS status;
|
|
static LARGE_INTEGER timeout = {(ULONG) -50000000, 0xFFFFFFFF };
|
|
|
|
PAGED_CODE();
|
|
|
|
KeInitializeEvent(&event, NotificationEvent, FALSE);
|
|
|
|
IoSetCompletionRoutine(Irp, HidpSynchronousCallCompletion, &event, TRUE, TRUE, TRUE);
|
|
|
|
status = HidpCallDriver(DeviceObject, Irp);
|
|
|
|
if (STATUS_PENDING == status) {
|
|
//
|
|
// Wait for 5 seconds. If we don't get a response within said amount
|
|
// of time, the device is being unresponsive (happens with some UPS').
|
|
// At that point, cancel the irp and return STATUS_IO_TIMEOUT.
|
|
//
|
|
status = KeWaitForSingleObject(&event,
|
|
Executive, // wait reason
|
|
KernelMode,
|
|
FALSE, // not alertable
|
|
&timeout ); // 5 second timeout
|
|
|
|
if (status == STATUS_TIMEOUT) {
|
|
#if DBG
|
|
LARGE_INTEGER li;
|
|
KeQueryTickCount(&li);
|
|
DBGWARN(("Could not cancel irp. Will have to wait. Time %x.",Irp,li))
|
|
#endif
|
|
DBGWARN(("Device didn't respond for 5 seconds. Cancelling request. Irp %x",Irp))
|
|
IoCancelIrp(Irp);
|
|
KeWaitForSingleObject(&event,
|
|
Executive, // wait reason
|
|
KernelMode,
|
|
FALSE, // not alertable
|
|
NULL ); // no timeout
|
|
#if DBG
|
|
KeQueryTickCount(&li);
|
|
DBGWARN(("Irp conpleted. Time %x.",li))
|
|
#endif
|
|
//
|
|
// If we successfully cancelled the irp, then set the status to
|
|
// STATUS_IO_TIMEOUT, otherwise, leave the status alone.
|
|
//
|
|
status = Irp->IoStatus.Status =
|
|
(Irp->IoStatus.Status == STATUS_CANCELLED) ? STATUS_IO_TIMEOUT : Irp->IoStatus.Status;
|
|
} else {
|
|
//
|
|
// The minidriver must always return STATUS_PENDING or STATUS_SUCCESS
|
|
// (depending on async or sync completion) and set the real status
|
|
// in the status block. We're not expecting anything but success from
|
|
// KeWaitForSingleObject, either.
|
|
//
|
|
status = Irp->IoStatus.Status;
|
|
}
|
|
}
|
|
|
|
DBGSUCCESS(status, FALSE)
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpMajorHandler
|
|
********************************************************************************
|
|
*
|
|
* Note: this function should not be pageable because
|
|
* reads can come in at dispatch level.
|
|
*
|
|
*/
|
|
NTSTATUS HidpMajorHandler(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
|
|
{
|
|
PHIDCLASS_DEVICE_EXTENSION hidClassExtension;
|
|
PIO_STACK_LOCATION irpSp;
|
|
NTSTATUS result;
|
|
UCHAR majorFunction;
|
|
BOOLEAN isClientPdo;
|
|
|
|
DBG_COMMON_ENTRY()
|
|
|
|
hidClassExtension = (PHIDCLASS_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
|
|
ASSERT(hidClassExtension->Signature == HID_DEVICE_EXTENSION_SIG);
|
|
|
|
//
|
|
// Get a pointer to the current stack location and dispatch to the
|
|
// appropriate routine.
|
|
//
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
/*
|
|
* Keep these privately so we still have it after the IRP completes
|
|
* or after the device extension is freed on a REMOVE_DEVICE
|
|
*/
|
|
majorFunction = irpSp->MajorFunction;
|
|
isClientPdo = hidClassExtension->isClientPdo;
|
|
|
|
DBG_LOG_IRP_MAJOR(Irp, majorFunction, isClientPdo, FALSE, 0)
|
|
|
|
switch (majorFunction){
|
|
|
|
case IRP_MJ_CLOSE:
|
|
result = HidpIrpMajorClose( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_CREATE:
|
|
result = HidpIrpMajorCreate( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_DEVICE_CONTROL:
|
|
result = HidpIrpMajorDeviceControl( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_INTERNAL_DEVICE_CONTROL:
|
|
result = HidpIrpMajorINTERNALDeviceControl( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_PNP:
|
|
result = HidpIrpMajorPnp( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_POWER:
|
|
result = HidpIrpMajorPower( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_READ:
|
|
result = HidpIrpMajorRead( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_WRITE:
|
|
result = HidpIrpMajorWrite( hidClassExtension, Irp );
|
|
break;
|
|
|
|
case IRP_MJ_SYSTEM_CONTROL:
|
|
result = HidpIrpMajorSystemControl( hidClassExtension, Irp );
|
|
break;
|
|
|
|
default:
|
|
result = HidpIrpMajorDefault( hidClassExtension, Irp );
|
|
break;
|
|
}
|
|
|
|
DBG_LOG_IRP_MAJOR(Irp, majorFunction, isClientPdo, TRUE, result)
|
|
|
|
DBG_COMMON_EXIT()
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpIrpMajorDefault
|
|
********************************************************************************
|
|
*
|
|
* Handle IRPs with un-handled MAJOR function codes
|
|
*
|
|
*/
|
|
NTSTATUS HidpIrpMajorDefault(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp)
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpSp;
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
DBGVERBOSE(("Unhandled IRP, MJ function: %x", irpSp->MajorFunction))
|
|
|
|
if (HidDeviceExtension->isClientPdo){
|
|
/*
|
|
* This IRP is bound for the collection-PDO.
|
|
* Return the default status.
|
|
*/
|
|
status = Irp->IoStatus.Status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
else {
|
|
/*
|
|
* This IRP is bound for the lower device.
|
|
* Pass it down the stack.
|
|
*/
|
|
FDO_EXTENSION *fdoExt = &HidDeviceExtension->fdoExt;
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
status = HidpCallDriver(fdoExt->fdo, Irp);
|
|
}
|
|
|
|
DBGSUCCESS(status, FALSE)
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpIrpMajorClose
|
|
********************************************************************************
|
|
*
|
|
* Note: this function cannot be pageable because it
|
|
* acquires a spinlock.
|
|
*
|
|
*/
|
|
NTSTATUS HidpIrpMajorClose(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp)
|
|
{
|
|
NTSTATUS result;
|
|
|
|
ASSERT(HidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG);
|
|
|
|
if (HidDeviceExtension->isClientPdo){
|
|
|
|
PIO_STACK_LOCATION irpSp;
|
|
PHIDCLASS_FILE_EXTENSION fileExtension;
|
|
PFILE_OBJECT fileObject;
|
|
KIRQL oldIrql;
|
|
PDO_EXTENSION *pdoExt;
|
|
FDO_EXTENSION *fdoExt;
|
|
ULONG openCount;
|
|
|
|
pdoExt = &HidDeviceExtension->pdoExt;
|
|
fdoExt = &pdoExt->deviceFdoExt->fdoExt;
|
|
|
|
ASSERT(fdoExt->openCount > 0);
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation( Irp );
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
|
|
fdoExt->openCount--;
|
|
openCount = fdoExt->openCount;
|
|
|
|
if (fileExtension){
|
|
PHIDCLASS_COLLECTION classCollection;
|
|
|
|
ASSERT(fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG);
|
|
|
|
/*
|
|
* Get a pointer to the collection that our file extension is queued on.
|
|
*/
|
|
classCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
if (classCollection){
|
|
|
|
DBGVERBOSE((" HidpIrpMajorClose: closing collection w/ usagePage=%xh, usage=%xh.", fdoExt->deviceDesc.CollectionDesc[pdoExt->collectionIndex].UsagePage, fdoExt->deviceDesc.CollectionDesc[pdoExt->collectionIndex].Usage))
|
|
|
|
if (fdoExt->state == DEVICE_STATE_REMOVED){
|
|
|
|
KeAcquireSpinLock( &classCollection->FileExtensionListSpinLock, &oldIrql );
|
|
RemoveEntryList(&fileExtension->FileList);
|
|
KeReleaseSpinLock( &classCollection->FileExtensionListSpinLock, oldIrql );
|
|
|
|
if (fileExtension->isSecureOpen) {
|
|
|
|
KeAcquireSpinLock(&classCollection->secureReadLock,
|
|
&oldIrql);
|
|
|
|
while(fileExtension->SecureReadMode--) {
|
|
|
|
classCollection->secureReadMode--;
|
|
|
|
}
|
|
|
|
KeReleaseSpinLock(&classCollection->secureReadLock,
|
|
oldIrql);
|
|
|
|
}
|
|
|
|
HidpDestroyFileExtension(classCollection, fileExtension);
|
|
|
|
classCollection = BAD_POINTER;
|
|
|
|
/*
|
|
* Delete the device-FDO and all collection-PDOs
|
|
* Don't touch fdoExt after this.
|
|
*/
|
|
HidpCleanUpFdo(fdoExt);
|
|
|
|
result = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
//
|
|
// Destroy the file object and everything on it
|
|
//
|
|
KeAcquireSpinLock(&classCollection->FileExtensionListSpinLock, &oldIrql);
|
|
|
|
/*
|
|
* Update sharing information:
|
|
* Decrement open counts and clear any exclusive holds of this file extension
|
|
* on the device extension.
|
|
*/
|
|
ASSERT(pdoExt->openCount > 0);
|
|
pdoExt->openCount--;
|
|
if (fileExtension->accessMask & FILE_READ_DATA){
|
|
ASSERT(pdoExt->opensForRead > 0);
|
|
pdoExt->opensForRead--;
|
|
}
|
|
if (fileExtension->accessMask & FILE_WRITE_DATA){
|
|
ASSERT(pdoExt->opensForWrite > 0);
|
|
pdoExt->opensForWrite--;
|
|
}
|
|
if (!(fileExtension->shareMask & FILE_SHARE_READ)){
|
|
ASSERT(pdoExt->restrictionsForRead > 0);
|
|
pdoExt->restrictionsForRead--;
|
|
}
|
|
if (!(fileExtension->shareMask & FILE_SHARE_WRITE)){
|
|
ASSERT(pdoExt->restrictionsForWrite > 0);
|
|
pdoExt->restrictionsForWrite--;
|
|
}
|
|
if (fileExtension->shareMask == 0){
|
|
ASSERT(pdoExt->restrictionsForAnyOpen > 0);
|
|
pdoExt->restrictionsForAnyOpen--;
|
|
}
|
|
|
|
RemoveEntryList(&fileExtension->FileList);
|
|
|
|
KeReleaseSpinLock(&classCollection->FileExtensionListSpinLock, oldIrql);
|
|
|
|
if (fileExtension->isSecureOpen) {
|
|
|
|
KeAcquireSpinLock(&classCollection->secureReadLock,
|
|
&oldIrql);
|
|
|
|
while(fileExtension->SecureReadMode--) {
|
|
|
|
classCollection->secureReadMode--;
|
|
|
|
}
|
|
|
|
KeReleaseSpinLock(&classCollection->secureReadLock,
|
|
oldIrql);
|
|
|
|
}
|
|
|
|
HidpDestroyFileExtension(classCollection, fileExtension);
|
|
|
|
result = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else {
|
|
result = STATUS_DATA_ERROR;
|
|
}
|
|
}
|
|
else {
|
|
TRAP;
|
|
result = STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
|
|
DBGVERBOSE((" HidpIrpMajorClose: openCount decremented to %xh/%xh (pdo/fdo).", openCount, fdoExt->openCount))
|
|
}
|
|
else {
|
|
DBGERR(("IRP_MJ_CLOSE was sent with a device-FDO extension for which an open never succeeded. The OBJDIR test tool does this sometimes. Hit 'g'."))
|
|
result = STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
Irp->IoStatus.Status = result;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
DBGSUCCESS(result, FALSE)
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpIrpMajorCreate
|
|
********************************************************************************
|
|
*
|
|
*
|
|
* Routine Description:
|
|
*
|
|
* We connect up to the interrupt for the create/open and initialize
|
|
* the structures needed to maintain an open for a device.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* DeviceObject - Pointer to the device object for this device
|
|
*
|
|
* Irp - Pointer to the IRP for the current request
|
|
*
|
|
* Return Value:
|
|
*
|
|
* The function value is the final status of the call
|
|
*
|
|
*/
|
|
NTSTATUS HidpIrpMajorCreate(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp)
|
|
{
|
|
NTSTATUS status = STATUS_SUCCESS;
|
|
|
|
ASSERT(HidDeviceExtension->Signature == HID_DEVICE_EXTENSION_SIG);
|
|
|
|
if (HidDeviceExtension->isClientPdo){
|
|
PDO_EXTENSION *pdoExt = &HidDeviceExtension->pdoExt;
|
|
FDO_EXTENSION *fdoExt = &pdoExt->deviceFdoExt->fdoExt;
|
|
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
PHIDCLASS_COLLECTION classCollection;
|
|
BOOLEAN secureOpen = FALSE;
|
|
|
|
secureOpen = MyPrivilegeCheck(Irp);
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
classCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
if (classCollection){
|
|
BOOLEAN sharingOk = TRUE, allowReadPrivilege = TRUE;
|
|
KIRQL oldIrql;
|
|
|
|
// This is now taken care of by the fact that we don't
|
|
// enumerate mouse and keyboard collections as RAW.
|
|
#if 0
|
|
/*
|
|
* Do security check
|
|
* (must be at passive level, so don't hold any spinlocks during this call)
|
|
*/
|
|
if (pdoExt->MouseOrKeyboard &&
|
|
!MyPrivilegeCheck(Irp)){
|
|
/*
|
|
* This is a user-level open on a keyboard or mouse.
|
|
* Disallow reads even if we do allow the open.
|
|
*/
|
|
allowReadPrivilege = FALSE;
|
|
}
|
|
#endif
|
|
|
|
KeAcquireSpinLock(&classCollection->FileExtensionListSpinLock, &oldIrql);
|
|
|
|
/*
|
|
* Enforce exclusive-open independently for exclusive-read and exclusive-write.
|
|
*/
|
|
ASSERT(irpSp->Parameters.Create.SecurityContext);
|
|
DBGVERBOSE((" HidpIrpMajorCreate: DesiredAccess = %xh, ShareAccess = %xh.", (ULONG)irpSp->Parameters.Create.SecurityContext->DesiredAccess, (ULONG)irpSp->Parameters.Create.ShareAccess))
|
|
|
|
DBGASSERT((irpSp->Parameters.Create.SecurityContext->DesiredAccess & (FILE_READ_DATA|FILE_WRITE_DATA)),
|
|
("Neither FILE_READ_DATA|FILE_WRITE_DATA requested in HidpIrpMajorCreate. DesiredAccess = %xh.", (ULONG)irpSp->Parameters.Create.SecurityContext->DesiredAccess),
|
|
FALSE)
|
|
if (pdoExt->restrictionsForAnyOpen){
|
|
/*
|
|
* Oops. A previous open requested exclusive access.
|
|
* Not even a client that requests only ioctl access
|
|
* (does not request read nor write acess) is
|
|
* allowed.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing open: previous open is non-shared (ShareAccess==0)."))
|
|
sharingOk = FALSE;
|
|
}
|
|
else if (pdoExt->openCount &&
|
|
(irpSp->Parameters.Create.ShareAccess == 0)){
|
|
/*
|
|
* Oops. This open does not allow any sharing
|
|
* (not even with a client that has neither read nor write access),
|
|
* but there exists a previous open.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing open: requesting non-shared (ShareAccess==0) while previous open exists."))
|
|
sharingOk = FALSE;
|
|
}
|
|
else if ((irpSp->Parameters.Create.SecurityContext->DesiredAccess & FILE_READ_DATA) &&
|
|
pdoExt->restrictionsForRead){
|
|
/*
|
|
* Oops. A previous open requested exclusive-read access.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing open: requesting read access while previous open does not share read access."))
|
|
sharingOk = FALSE;
|
|
}
|
|
else if ((irpSp->Parameters.Create.SecurityContext->DesiredAccess & FILE_WRITE_DATA) &&
|
|
pdoExt->restrictionsForWrite){
|
|
/*
|
|
* Oops. A previous open requested exclusive-write access.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing open: requesting write access while previous open does not share write access."))
|
|
sharingOk = FALSE;
|
|
}
|
|
else if ((pdoExt->opensForRead > 0) &&
|
|
!(irpSp->Parameters.Create.ShareAccess & FILE_SHARE_READ)){
|
|
|
|
/*
|
|
* Oops. The caller is requesting exclusive read access, but the device
|
|
* is already open for read.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing open: this open request does not share read access; but collection already open for read."))
|
|
sharingOk = FALSE;
|
|
}
|
|
else if ((pdoExt->opensForWrite > 0) &&
|
|
!(irpSp->Parameters.Create.ShareAccess & FILE_SHARE_WRITE)){
|
|
|
|
/*
|
|
* Oops. The caller is requesting exclusive write access, but the device
|
|
* is already open for write.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing open: this open request does not share write access; but collection already open for write."))
|
|
sharingOk = FALSE;
|
|
}
|
|
|
|
|
|
if (!sharingOk){
|
|
DBGWARN(("HidpIrpMajorCreate failing IRP_MJ_CREATE with STATUS_SHARING_VIOLATION."))
|
|
status = STATUS_SHARING_VIOLATION;
|
|
}
|
|
else if (!allowReadPrivilege &&
|
|
(irpSp->Parameters.Create.SecurityContext->DesiredAccess & FILE_READ_DATA)){
|
|
DBGWARN(("HidpIrpMajorCreate failing open: user-mode client requesting read privilege on kb/mouse."))
|
|
status = STATUS_ACCESS_DENIED;
|
|
}
|
|
else {
|
|
if (irpSp->Parameters.Create.Options & FILE_DIRECTORY_FILE){
|
|
/*
|
|
* Attempt to open this device as a directory
|
|
*/
|
|
status = STATUS_NOT_A_DIRECTORY;
|
|
} else {
|
|
|
|
/*
|
|
* Make sure the device is started.
|
|
* If it is temporarily stopped, we also succeed because a stop
|
|
* is supposed to be transparent to the client.
|
|
*/
|
|
if (((fdoExt->state == DEVICE_STATE_START_SUCCESS) ||
|
|
(fdoExt->state == DEVICE_STATE_STOPPING) ||
|
|
(fdoExt->state == DEVICE_STATE_STOPPED))
|
|
&&
|
|
((pdoExt->state == COLLECTION_STATE_RUNNING) ||
|
|
(pdoExt->state == COLLECTION_STATE_STOPPING) ||
|
|
(pdoExt->state == COLLECTION_STATE_STOPPED))){
|
|
|
|
PHIDCLASS_FILE_EXTENSION fileExtension;
|
|
|
|
/*
|
|
* We have a valid collection.
|
|
* Allocate a file object extension (which encapsulates an 'open' on the device).
|
|
*/
|
|
fileExtension = ALLOCATEPOOL(NonPagedPool, sizeof(HIDCLASS_FILE_EXTENSION));
|
|
if (fileExtension){
|
|
PHIDP_COLLECTION_DESC hidCollectionDesc;
|
|
|
|
RtlZeroMemory(fileExtension, sizeof(HIDCLASS_FILE_EXTENSION));
|
|
|
|
fileExtension->CollectionNumber = pdoExt->collectionNum;
|
|
fileExtension->fdoExt = fdoExt;
|
|
fileExtension->FileObject = irpSp->FileObject;
|
|
|
|
fileExtension->isOpportunisticPolledDeviceReader = FALSE;
|
|
fileExtension->nowCompletingIrpForOpportunisticReader = 0;
|
|
|
|
fileExtension->BlueScreenData.BluescreenFunction = NULL;
|
|
|
|
InitializeListHead( &fileExtension->ReportList );
|
|
InitializeListHead( &fileExtension->PendingIrpList );
|
|
KeInitializeSpinLock( &fileExtension->ListSpinLock );
|
|
fileExtension->Closing = FALSE;
|
|
|
|
//
|
|
// Right now we'll set a default maximum input report queue size.
|
|
// This can be changed later with an IOCTL.
|
|
//
|
|
|
|
fileExtension->CurrentInputReportQueueSize = 0;
|
|
fileExtension->MaximumInputReportQueueSize = DEFAULT_INPUT_REPORT_QUEUE_SIZE;
|
|
fileExtension->insideReadCompleteCount = 0;
|
|
|
|
//
|
|
// Add this file extension to the list of file extensions for this
|
|
// collection.
|
|
//
|
|
|
|
InsertHeadList(&classCollection->FileExtensionList, &fileExtension->FileList);
|
|
|
|
#if DBG
|
|
fileExtension->Signature = HIDCLASS_FILE_EXTENSION_SIG;
|
|
#endif
|
|
|
|
/*
|
|
* Store the file-open attribute flags.
|
|
*/
|
|
fileExtension->FileAttributes = irpSp->Parameters.Create.FileAttributes;
|
|
fileExtension->accessMask = irpSp->Parameters.Create.SecurityContext->DesiredAccess;
|
|
fileExtension->shareMask = irpSp->Parameters.Create.ShareAccess;
|
|
|
|
|
|
//
|
|
// Set up secure read mode
|
|
//
|
|
fileExtension->SecureReadMode = 0;
|
|
fileExtension->isSecureOpen = secureOpen;
|
|
|
|
|
|
/*
|
|
* Store a pointer to our file extension in the file object.
|
|
*/
|
|
irpSp->FileObject->FsContext = fileExtension;
|
|
|
|
//
|
|
// KENRAY
|
|
// Only drivers can set the FsContext of file
|
|
// objects so this is not a security problem.
|
|
// However, there is only one file object for the entire
|
|
// PDO stack. This means we have to share. You cannot
|
|
// have both context pointers. I need one for the
|
|
// keyboard and mouse class drivers.
|
|
//
|
|
// This information need go into the fileExtension.
|
|
//
|
|
fileExtension->SecurityCheck = TRUE;
|
|
|
|
//
|
|
// If this is a user-level open on a keyboard or
|
|
// mouse, then we will let the client do anything
|
|
// except read the device.
|
|
//
|
|
DBGASSERT(allowReadPrivilege,
|
|
("User-level open on keyboard or mouse. Reads will be disallowed."),
|
|
FALSE)
|
|
|
|
fileExtension->haveReadPrivilege = allowReadPrivilege;
|
|
|
|
/*
|
|
* Increment the device extension's open counts,
|
|
* and set the exclusive-access fields.
|
|
*/
|
|
fdoExt->openCount++;
|
|
pdoExt->openCount++;
|
|
if (irpSp->Parameters.Create.SecurityContext->DesiredAccess & FILE_READ_DATA){
|
|
pdoExt->opensForRead++;
|
|
}
|
|
if (irpSp->Parameters.Create.SecurityContext->DesiredAccess & FILE_WRITE_DATA){
|
|
pdoExt->opensForWrite++;
|
|
}
|
|
|
|
/*
|
|
* NOTE: Restrictions are independent of desired access.
|
|
* For example, a client can do an open-for-read-only
|
|
* AND prevent other clients from doing an open-for-write
|
|
* (by not setting the FILE_SHARE_WRITE flag).
|
|
*/
|
|
if (!(irpSp->Parameters.Create.ShareAccess & FILE_SHARE_READ)){
|
|
pdoExt->restrictionsForRead++;
|
|
}
|
|
if (!(irpSp->Parameters.Create.ShareAccess & FILE_SHARE_WRITE)){
|
|
pdoExt->restrictionsForWrite++;
|
|
}
|
|
if (irpSp->Parameters.Create.ShareAccess == 0){
|
|
/*
|
|
* ShareAccess==0 means that no other opens of any kind
|
|
* are allowed.
|
|
*/
|
|
pdoExt->restrictionsForAnyOpen++;
|
|
}
|
|
|
|
DBGVERBOSE((" HidpIrpMajorCreate: opened collection w/ usagePage=%xh, usage=%xh. openCount incremented to %xh/%xh (pdo/fdo).", fdoExt->deviceDesc.CollectionDesc[pdoExt->collectionIndex].UsagePage, fdoExt->deviceDesc.CollectionDesc[pdoExt->collectionIndex].Usage, pdoExt->openCount, fdoExt->openCount))
|
|
} else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
} else {
|
|
status = STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
KeReleaseSpinLock(&classCollection->FileExtensionListSpinLock, oldIrql);
|
|
}
|
|
else {
|
|
DBGERR(("HidpIrpMajorCreate failing -- couldn't find collection"))
|
|
status = STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* We don't support opens on the device itself,
|
|
* only on the collections.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorCreate failing -- we don't support opens on the device itself; only on collections."))
|
|
status = STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
DBGSUCCESS(status, FALSE)
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpIrpMajorDeviceControl
|
|
********************************************************************************
|
|
*
|
|
* Note: This function cannot be pageable because IOCTLs
|
|
* can get sent at DISPATCH_LEVEL.
|
|
*
|
|
*/
|
|
NTSTATUS HidpIrpMajorDeviceControl(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp)
|
|
{
|
|
NTSTATUS status;
|
|
BOOLEAN completeIrpHere = TRUE;
|
|
PDO_EXTENSION *pdoExt;
|
|
FDO_EXTENSION *fdoExt;
|
|
ULONG ioControlCode;
|
|
|
|
PIO_STACK_LOCATION irpSp;
|
|
|
|
PHIDCLASS_COLLECTION hidCollection;
|
|
PHIDCLASS_FILE_EXTENSION fileExtension;
|
|
PFILE_OBJECT fileObject;
|
|
KIRQL irql;
|
|
|
|
if (!HidDeviceExtension->isClientPdo){
|
|
ASSERT(HidDeviceExtension->isClientPdo);
|
|
status = STATUS_INVALID_PARAMETER_1;
|
|
goto HidpIrpMajorDeviceControlDone;
|
|
}
|
|
|
|
pdoExt = &HidDeviceExtension->pdoExt;
|
|
fdoExt = &pdoExt->deviceFdoExt->fdoExt;
|
|
|
|
if (fdoExt->state != DEVICE_STATE_START_SUCCESS ||
|
|
pdoExt->state != COLLECTION_STATE_RUNNING) {
|
|
DBGSTATE (pdoExt->state, COLLECTION_STATE_RUNNING, FALSE)
|
|
status = STATUS_DEVICE_NOT_CONNECTED;
|
|
goto HidpIrpMajorDeviceControlDone;
|
|
}
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
// Keep this privately so we still have it after the IRP is completed.
|
|
ioControlCode = irpSp->Parameters.DeviceIoControl.IoControlCode;
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
status = HidpCheckIdleState(HidDeviceExtension, Irp);
|
|
if (status != STATUS_SUCCESS) {
|
|
completeIrpHere = (status != STATUS_PENDING);
|
|
goto HidpIrpMajorDeviceControlDone;
|
|
}
|
|
|
|
switch (ioControlCode){
|
|
|
|
case IOCTL_HID_GET_DRIVER_CONFIG:
|
|
TRAP;
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
case IOCTL_HID_SET_DRIVER_CONFIG:
|
|
TRAP;
|
|
status = STATUS_NOT_IMPLEMENTED;
|
|
break;
|
|
|
|
case IOCTL_HID_GET_COLLECTION_INFORMATION:
|
|
/*
|
|
* This IRP is METHOD_BUFFERED, so the buffer
|
|
* is in the AssociatedIrp.
|
|
*/
|
|
DBGASSERT((Irp->Flags & IRP_BUFFERED_IO),
|
|
("Irp->Flags & IRP_BUFFERED_IO Irp->Type != IO_TYPE_IRP, Irp->Type == %x", Irp->Type),
|
|
FALSE)
|
|
if (Irp->AssociatedIrp.SystemBuffer){
|
|
ULONG bufLen = irpSp->Parameters.DeviceIoControl.OutputBufferLength;
|
|
status = HidpGetCollectionInformation(
|
|
fdoExt,
|
|
pdoExt->collectionNum,
|
|
Irp->AssociatedIrp.SystemBuffer,
|
|
&bufLen);
|
|
Irp->IoStatus.Information = bufLen;
|
|
}
|
|
else {
|
|
ASSERT(Irp->AssociatedIrp.SystemBuffer);
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_HID_GET_COLLECTION_DESCRIPTOR:
|
|
/*
|
|
* This IOCTL is METHOD_NEITHER, so the buffer is in UserBuffer.
|
|
*/
|
|
if (Irp->UserBuffer){
|
|
__try {
|
|
ULONG bufLen = irpSp->Parameters.DeviceIoControl.OutputBufferLength;
|
|
|
|
if (Irp->RequestorMode != KernelMode){
|
|
/*
|
|
* Ensure user-mode buffer is legal.
|
|
*/
|
|
ProbeForWrite(Irp->UserBuffer, bufLen, sizeof(UCHAR));
|
|
}
|
|
|
|
status = HidpGetCollectionDescriptor(
|
|
fdoExt,
|
|
pdoExt->collectionNum,
|
|
Irp->UserBuffer,
|
|
&bufLen);
|
|
Irp->IoStatus.Information = bufLen;
|
|
}
|
|
__except(EXCEPTION_EXECUTE_HANDLER) {
|
|
TRAP;
|
|
status = GetExceptionCode();
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(Irp->UserBuffer);
|
|
status = STATUS_INVALID_BUFFER_SIZE;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_HID_FLUSH_QUEUE:
|
|
|
|
//
|
|
// Run the list of report descriptors hanging off of this
|
|
// file object and free them all.
|
|
//
|
|
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to flush queue with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
ASSERT(fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG);
|
|
HidpFlushReportQueue(fileExtension);
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IOCTL_HID_GET_POLL_FREQUENCY_MSEC:
|
|
hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
if (hidCollection && hidCollection->hidCollectionInfo.Polled){
|
|
/*
|
|
* Get the current poll frequency.
|
|
* This IOCTL is METHOD_BUFFERED, so the result goes in the AssociatedIrp.
|
|
*/
|
|
DBGASSERT((Irp->Flags & IRP_BUFFERED_IO),
|
|
("Irp->Flags & IRP_BUFFERED_IO Irp->Type != IO_TYPE_IRP, Irp->Type == %x", Irp->Type),
|
|
FALSE)
|
|
if (Irp->AssociatedIrp.SystemBuffer &&
|
|
(irpSp->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(ULONG))){
|
|
|
|
*(ULONG *)Irp->AssociatedIrp.SystemBuffer = hidCollection->PollInterval_msec;
|
|
Irp->IoStatus.Information = sizeof (ULONG);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
ASSERT(!"Bad SystemBuffer for IOCTL_HID_GET_POLL_FREQUENCY_MSEC.");
|
|
status = STATUS_INVALID_BUFFER_SIZE;
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_HID_SET_POLL_FREQUENCY_MSEC:
|
|
hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
if (hidCollection && hidCollection->hidCollectionInfo.Polled){
|
|
|
|
if (Irp->AssociatedIrp.SystemBuffer &&
|
|
(irpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(ULONG))){
|
|
|
|
ULONG newPollInterval = *(ULONG *)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to set poll frequency with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
ASSERT(fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG);
|
|
|
|
if (newPollInterval == 0){
|
|
/*
|
|
* Poll interval zero means that this client will
|
|
* be doing irregular, opportunistic reads on the
|
|
* polled device. We will not change the polling
|
|
* frequency of the device. But when this client
|
|
* does a read, we will immediately complete that read
|
|
* with either the last report for this collection
|
|
* (if the data is not stale) or by immediately issuing
|
|
* a new read.
|
|
*/
|
|
fileExtension->isOpportunisticPolledDeviceReader = TRUE;
|
|
}
|
|
else {
|
|
/*
|
|
* Set the poll frequency AND tell the user what we really set it to
|
|
* in case it's out of range.
|
|
*/
|
|
if (newPollInterval < MIN_POLL_INTERVAL_MSEC){
|
|
newPollInterval = MIN_POLL_INTERVAL_MSEC;
|
|
}
|
|
else if (newPollInterval > MAX_POLL_INTERVAL_MSEC){
|
|
newPollInterval = MAX_POLL_INTERVAL_MSEC;
|
|
}
|
|
hidCollection->PollInterval_msec = newPollInterval;
|
|
|
|
/*
|
|
* If this client was an 'opportunistic' reader before,
|
|
* he's not anymore.
|
|
*/
|
|
fileExtension->isOpportunisticPolledDeviceReader = FALSE;
|
|
|
|
/*
|
|
* Stop and re-start the polling loop so that
|
|
* the new polling interval takes effect right away.
|
|
*/
|
|
StopPollingLoop(hidCollection, FALSE);
|
|
StartPollingLoop(fdoExt, hidCollection, FALSE);
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
ASSERT(!"Bad SystemBuffer for IOCTL_HID_SET_POLL_FREQUENCY_MSEC.");
|
|
status = STATUS_INVALID_BUFFER_SIZE;
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INVALID_DEVICE_REQUEST;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_HID_GET_FEATURE:
|
|
case IOCTL_HID_SET_FEATURE:
|
|
#if 0
|
|
{
|
|
BOOLEAN sentIrpToMinidriver;
|
|
status = HidpGetSetFeature( HidDeviceExtension,
|
|
Irp,
|
|
irpSp->Parameters.DeviceIoControl.IoControlCode,
|
|
&sentIrpToMinidriver);
|
|
/*
|
|
* If we just passed this Irp to the minidriver, we don't want to
|
|
* complete the Irp; we're not even allowed to touch it since it may
|
|
* have already completed.
|
|
*/
|
|
completeIrpHere = !sentIrpToMinidriver;
|
|
}
|
|
break;
|
|
#endif
|
|
case IOCTL_HID_GET_INPUT_REPORT:
|
|
case IOCTL_HID_SET_OUTPUT_REPORT:
|
|
{
|
|
BOOLEAN sentIrpToMinidriver;
|
|
status = HidpGetSetReport ( HidDeviceExtension,
|
|
Irp,
|
|
irpSp->Parameters.DeviceIoControl.IoControlCode,
|
|
&sentIrpToMinidriver);
|
|
/*
|
|
* If we just passed this Irp to the minidriver, we don't want to
|
|
* complete the Irp; we're not even allowed to touch it since it may
|
|
* have already completed.
|
|
*/
|
|
completeIrpHere = !sentIrpToMinidriver;
|
|
}
|
|
break;
|
|
|
|
// NOTE - we currently only support English (langId=0x0409).
|
|
// route all collection-PDO string requests to device-FDO.
|
|
case IOCTL_HID_GET_MANUFACTURER_STRING:
|
|
status = HidpGetDeviceString(fdoExt, Irp, HID_STRING_ID_IMANUFACTURER, 0x0409);
|
|
completeIrpHere = FALSE;
|
|
break;
|
|
|
|
case IOCTL_HID_GET_PRODUCT_STRING:
|
|
status = HidpGetDeviceString(fdoExt, Irp, HID_STRING_ID_IPRODUCT, 0x0409);
|
|
completeIrpHere = FALSE;
|
|
break;
|
|
|
|
case IOCTL_HID_GET_SERIALNUMBER_STRING:
|
|
status = HidpGetDeviceString(fdoExt, Irp, HID_STRING_ID_ISERIALNUMBER, 0x0409);
|
|
completeIrpHere = FALSE;
|
|
break;
|
|
|
|
case IOCTL_HID_GET_INDEXED_STRING:
|
|
/*
|
|
* This IRP is METHOD_OUT_DIRECT, so the buffer is in the MDL.
|
|
* The second argument (string index) is in the AssociatedIrp;
|
|
* the InputBufferLength is the length of this second buffer.
|
|
*/
|
|
if (Irp->AssociatedIrp.SystemBuffer &&
|
|
(irpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(ULONG))){
|
|
|
|
ULONG stringIndex = *(ULONG *)Irp->AssociatedIrp.SystemBuffer;
|
|
status = HidpGetIndexedString(fdoExt, Irp, stringIndex, 0x409);
|
|
completeIrpHere = FALSE;
|
|
}
|
|
else {
|
|
ASSERT(Irp->AssociatedIrp.SystemBuffer);
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_HID_GET_MS_GENRE_DESCRIPTOR:
|
|
/*
|
|
* This IRP is METHOD_OUT_DIRECT, so the buffer is in the MDL.
|
|
*/
|
|
status = HidpGetMsGenreDescriptor(fdoExt, Irp);
|
|
completeIrpHere = FALSE;
|
|
break;
|
|
|
|
case IOCTL_GET_NUM_DEVICE_INPUT_BUFFERS:
|
|
|
|
/*
|
|
* This IRP is METHOD_BUFFERED, so the buffer
|
|
* is in the AssociatedIrp.SystemBuffer field.
|
|
*/
|
|
DBGASSERT((Irp->Flags & IRP_BUFFERED_IO),
|
|
("Irp->Flags & IRP_BUFFERED_IO Irp->Type != IO_TYPE_IRP, Irp->Type == %x", Irp->Type),
|
|
FALSE)
|
|
if (Irp->AssociatedIrp.SystemBuffer &&
|
|
(irpSp->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(ULONG))){
|
|
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to get number of input buffers with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
ASSERT( fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG );
|
|
|
|
*(ULONG *)Irp->AssociatedIrp.SystemBuffer =
|
|
fileExtension->MaximumInputReportQueueSize;
|
|
Irp->IoStatus.Information = sizeof(ULONG);
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
ASSERT(Irp->AssociatedIrp.SystemBuffer);
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_SET_NUM_DEVICE_INPUT_BUFFERS:
|
|
|
|
/*
|
|
* This IRP is METHOD_BUFFERED, so the buffer
|
|
* is in the AssociatedIrp.SystemBuffer field.
|
|
*/
|
|
DBGASSERT((Irp->Flags & IRP_BUFFERED_IO),
|
|
("Irp->Flags & IRP_BUFFERED_IO Irp->Type != IO_TYPE_IRP, Irp->Type == %x", Irp->Type),
|
|
FALSE)
|
|
if (Irp->AssociatedIrp.SystemBuffer &&
|
|
(irpSp->Parameters.DeviceIoControl.InputBufferLength >= sizeof(ULONG))){
|
|
|
|
ULONG newValue = *(ULONG *)Irp->AssociatedIrp.SystemBuffer;
|
|
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to set number of input buffers with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
ASSERT( fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG );
|
|
|
|
if ((newValue >= MIN_INPUT_REPORT_QUEUE_SIZE) &&
|
|
(newValue <= MAX_INPUT_REPORT_QUEUE_SIZE)){
|
|
|
|
fileExtension->MaximumInputReportQueueSize = newValue;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(Irp->AssociatedIrp.SystemBuffer);
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_GET_PHYSICAL_DESCRIPTOR:
|
|
status = HidpGetPhysicalDescriptor(HidDeviceExtension, Irp);
|
|
completeIrpHere = FALSE;
|
|
break;
|
|
|
|
case IOCTL_HID_GET_HARDWARE_ID:
|
|
{
|
|
PDEVICE_OBJECT pdo = pdoExt->deviceFdoExt->hidExt.PhysicalDeviceObject;
|
|
ULONG bufLen = irpSp->Parameters.DeviceIoControl.OutputBufferLength;
|
|
PWSTR hwIdBuf;
|
|
|
|
hwIdBuf = HidpGetSystemAddressForMdlSafe(Irp->MdlAddress);
|
|
|
|
if (hwIdBuf && bufLen){
|
|
ULONG actualLen;
|
|
|
|
status = IoGetDeviceProperty( pdo,
|
|
DevicePropertyHardwareID,
|
|
bufLen,
|
|
hwIdBuf,
|
|
&actualLen);
|
|
if (NT_SUCCESS(status)){
|
|
Irp->IoStatus.Information = (ULONG)actualLen;
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INVALID_USER_BUFFER;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case IOCTL_GET_SYS_BUTTON_CAPS:
|
|
hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
if (hidCollection){
|
|
if (irpSp->Parameters.DeviceIoControl.OutputBufferLength >= sizeof(ULONG)){
|
|
ULONG buttonCaps;
|
|
|
|
status = HidP_SysPowerCaps(hidCollection->phidDescriptor, &buttonCaps);
|
|
if (NT_SUCCESS(status)){
|
|
*(PULONG)Irp->AssociatedIrp.SystemBuffer = buttonCaps;
|
|
Irp->IoStatus.Information = sizeof(ULONG);
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_INVALID_BUFFER_SIZE;
|
|
Irp->IoStatus.Information = sizeof(ULONG);
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_GET_SYS_BUTTON_EVENT:
|
|
|
|
/*
|
|
* Hold onto this IRP and complete it when a power event occurs.
|
|
*/
|
|
hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
if (hidCollection){
|
|
status = QueuePowerEventIrp(hidCollection, Irp);
|
|
if (status == STATUS_PENDING){
|
|
completeIrpHere = FALSE;
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
break;
|
|
|
|
case IOCTL_HID_ENABLE_SECURE_READ:
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to get number of input buffers with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
ASSERT( fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG );
|
|
|
|
hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
|
|
if (!fileExtension->isSecureOpen) {
|
|
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
|
|
}
|
|
|
|
KeAcquireSpinLock(&hidCollection->secureReadLock,
|
|
&irql);
|
|
fileExtension->SecureReadMode++;
|
|
hidCollection->secureReadMode++;
|
|
|
|
KeReleaseSpinLock(&hidCollection->secureReadLock,
|
|
irql);
|
|
|
|
|
|
|
|
break;
|
|
case IOCTL_HID_DISABLE_SECURE_READ:
|
|
|
|
fileObject = irpSp->FileObject;
|
|
fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to get number of input buffers with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
}
|
|
ASSERT( fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG );
|
|
|
|
hidCollection = GetHidclassCollection(fdoExt, pdoExt->collectionNum);
|
|
|
|
if (!fileExtension->isSecureOpen) {
|
|
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
break;
|
|
|
|
}
|
|
|
|
KeAcquireSpinLock(&hidCollection->secureReadLock,
|
|
&irql);
|
|
if (fileExtension->SecureReadMode > 0) {
|
|
fileExtension->SecureReadMode--;
|
|
hidCollection->secureReadMode--;
|
|
}
|
|
|
|
KeReleaseSpinLock(&hidCollection->secureReadLock,
|
|
irql);
|
|
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* 'Fail' the Irp by returning the default status.
|
|
*/
|
|
TRAP;
|
|
status = Irp->IoStatus.Status;
|
|
break;
|
|
}
|
|
|
|
DBG_LOG_IOCTL(fdoExt->fdo, ioControlCode, status)
|
|
|
|
HidpIrpMajorDeviceControlDone:
|
|
|
|
/*
|
|
* If we did not pass the Irp down to a lower driver, complete it here.
|
|
*/
|
|
if (completeIrpHere){
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
DBGSUCCESS(status, FALSE)
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpIrpMajorINTERNALDeviceControl
|
|
********************************************************************************
|
|
*
|
|
* Note: This function cannot be pageable because IOCTLs
|
|
* can get sent at DISPATCH_LEVEL.
|
|
*
|
|
*/
|
|
NTSTATUS HidpIrpMajorINTERNALDeviceControl(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
if (HidDeviceExtension->isClientPdo){
|
|
PDO_EXTENSION *pdoExt = &HidDeviceExtension->pdoExt;
|
|
FDO_EXTENSION *fdoExt = &pdoExt->deviceFdoExt->fdoExt;
|
|
|
|
Irp->IoStatus.Information = 0;
|
|
|
|
//
|
|
// If we ever support any other internal IOCTLs that are real and
|
|
// require touching the hardware, then we need to break out the check
|
|
// for fdoExt->devicePowerState and enqueue the irp until we get to full
|
|
// power
|
|
//
|
|
if (fdoExt->state == DEVICE_STATE_START_SUCCESS) {
|
|
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
switch (irpSp->Parameters.DeviceIoControl.IoControlCode){
|
|
|
|
case IOCTL_INTERNAL_HID_SET_BLUESCREEN:
|
|
|
|
/*
|
|
* Memphis code to handle keyboards during blue screen event
|
|
*/
|
|
if (Irp->AssociatedIrp.SystemBuffer){
|
|
PFILE_OBJECT fileObject = irpSp->FileObject;
|
|
PHIDCLASS_FILE_EXTENSION fileExtension = (PHIDCLASS_FILE_EXTENSION)fileObject->FsContext;
|
|
if(!fileExtension) {
|
|
DBGWARN(("Attempted to set blue screen data with no file extension"))
|
|
status = STATUS_PRIVILEGE_NOT_HELD;
|
|
} else {
|
|
ASSERT( fileExtension->Signature == HIDCLASS_FILE_EXTENSION_SIG );
|
|
ASSERT(irpSp->Parameters.DeviceIoControl.InputBufferLength == sizeof(BLUESCREEN));
|
|
|
|
fileExtension->BlueScreenData = *((PBLUESCREEN)Irp->AssociatedIrp.SystemBuffer);
|
|
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(Irp->AssociatedIrp.SystemBuffer);
|
|
status = STATUS_INVALID_PARAMETER;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* 'Fail' the Irp by returning the default status.
|
|
*/
|
|
DBGWARN(("HidpIrpMajorINTERNALDeviceControl - unsupported IOCTL %xh ", (ULONG)irpSp->Parameters.DeviceIoControl.IoControlCode))
|
|
status = Irp->IoStatus.Status;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
status = STATUS_DEVICE_NOT_CONNECTED;
|
|
}
|
|
}
|
|
else {
|
|
ASSERT(HidDeviceExtension->isClientPdo);
|
|
status = STATUS_INVALID_PARAMETER_1;
|
|
}
|
|
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
DBGSUCCESS(status, FALSE)
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
********************************************************************************
|
|
* HidpIrpMajorPnp
|
|
********************************************************************************
|
|
*
|
|
*
|
|
*/
|
|
NTSTATUS HidpIrpMajorPnp(IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension, IN OUT PIRP Irp)
|
|
{
|
|
NTSTATUS status;
|
|
PIO_STACK_LOCATION irpSp;
|
|
BOOLEAN completeIrpHere;
|
|
BOOLEAN isClientPdo;
|
|
UCHAR minorFunction;
|
|
|
|
PAGED_CODE();
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
/*
|
|
* Keep these fields privately so that we have them
|
|
* after the IRP is completed and in case we delete
|
|
* the device extension on a REMOVE_DEVICE.
|
|
*/
|
|
isClientPdo = HidDeviceExtension->isClientPdo;
|
|
minorFunction = irpSp->MinorFunction;
|
|
|
|
DBG_LOG_PNP_IRP(Irp, minorFunction, isClientPdo, FALSE, 0)
|
|
|
|
if (isClientPdo) {
|
|
status = HidpPdoPnp(HidDeviceExtension, Irp);
|
|
} else {
|
|
status = HidpFdoPnp(HidDeviceExtension, Irp);
|
|
}
|
|
|
|
DBG_LOG_PNP_IRP(Irp, minorFunction, isClientPdo, TRUE, status)
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS HidpPdoPnp(
|
|
IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
|
|
IN OUT PIRP Irp
|
|
)
|
|
{
|
|
NTSTATUS status = NO_STATUS;
|
|
PIO_STACK_LOCATION irpSp;
|
|
FDO_EXTENSION *fdoExt;
|
|
PDO_EXTENSION *pdoExt;
|
|
UCHAR minorFunction;
|
|
|
|
PAGED_CODE();
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
/*
|
|
* Keep these fields privately so that we have them
|
|
* after the IRP is completed and in case we delete
|
|
* the device extension on a REMOVE_DEVICE.
|
|
*/
|
|
minorFunction = irpSp->MinorFunction;
|
|
|
|
|
|
DBG_LOG_PNP_IRP(Irp, minorFunction, TRUE, FALSE, 0)
|
|
|
|
pdoExt = &HidDeviceExtension->pdoExt;
|
|
fdoExt = &pdoExt->deviceFdoExt->fdoExt;
|
|
|
|
switch (minorFunction){
|
|
|
|
case IRP_MN_START_DEVICE:
|
|
status = HidpStartCollectionPDO(fdoExt, pdoExt, Irp);
|
|
if (NT_SUCCESS(status) &&
|
|
ISPTR(pdoExt->StatusChangeFn)) {
|
|
pdoExt->StatusChangeFn(pdoExt->StatusChangeContext,
|
|
DeviceObjectStarted);
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_QUERY_STOP_DEVICE:
|
|
DBGSTATE(pdoExt->state, COLLECTION_STATE_RUNNING, FALSE)
|
|
pdoExt->prevState = pdoExt->state;
|
|
pdoExt->state = COLLECTION_STATE_STOPPING;
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IRP_MN_CANCEL_STOP_DEVICE:
|
|
DBGSTATE(pdoExt->state, COLLECTION_STATE_STOPPING, TRUE)
|
|
pdoExt->state = pdoExt->prevState;
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IRP_MN_STOP_DEVICE:
|
|
DBGSTATE(pdoExt->state, COLLECTION_STATE_STOPPING, TRUE)
|
|
if (pdoExt->prevState != COLLECTION_STATE_UNINITIALIZED){
|
|
/*
|
|
* Destroy the symbolic link for this collection.
|
|
*/
|
|
HidpCreateSymbolicLink(pdoExt, pdoExt->collectionNum, FALSE, pdoExt->pdo);
|
|
HidpFreePowerEventIrp(&fdoExt->classCollectionArray[pdoExt->collectionIndex]);
|
|
|
|
pdoExt->state = COLLECTION_STATE_STOPPED;
|
|
if (ISPTR(pdoExt->StatusChangeFn)) {
|
|
pdoExt->StatusChangeFn(pdoExt->StatusChangeContext,
|
|
DeviceObjectStopped);
|
|
}
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IRP_MN_SURPRISE_REMOVAL:
|
|
case IRP_MN_QUERY_REMOVE_DEVICE:
|
|
DBGASSERT(((pdoExt->state == COLLECTION_STATE_RUNNING) ||
|
|
(pdoExt->state == COLLECTION_STATE_STOPPED)),
|
|
("Pdo is neither stopped nor started, but is getting removed, state=%d",pdoExt->state),
|
|
FALSE)
|
|
|
|
pdoExt->prevState = pdoExt->state;
|
|
pdoExt->state = COLLECTION_STATE_REMOVING;
|
|
|
|
if ((pdoExt->prevState == COLLECTION_STATE_RUNNING)) {
|
|
|
|
/*
|
|
* Remove the symbolic link for this collection-PDO.
|
|
*
|
|
* NOTE: Do this BEFORE destroying the collection, because
|
|
* HidpDestroyCollection() may cause a client driver,
|
|
* whose pending read IRPs get cancelled when the collection
|
|
* is destroyed, to try to re-open the device.
|
|
* Deleting the symbolic link first eliminates this possibility.
|
|
*/
|
|
HidpCreateSymbolicLink(pdoExt, pdoExt->collectionNum, FALSE, pdoExt->pdo);
|
|
}
|
|
|
|
if ((pdoExt->prevState == COLLECTION_STATE_RUNNING) ||
|
|
(pdoExt->prevState == COLLECTION_STATE_STOPPED)){
|
|
|
|
/*
|
|
* Flush all pending IO and deny any future io by setting
|
|
* the collection state to removing.
|
|
* Note: on NT, clients will receive the query remove
|
|
* first, but surprise removal must deny access to the
|
|
* device.
|
|
*
|
|
* NOTE: There is a hole here that results in a read being
|
|
* queued even though we've blocked everything.
|
|
* 1) Get read, check to see that our state is running
|
|
* or stopped in HidpIrpMajorRead.
|
|
* 2) Set state to COLLECTION_STATE_REMOVING and complete
|
|
* all reads here.
|
|
* 3) Enqueue read in HidpIrpMajorRead.
|
|
*
|
|
*/
|
|
ULONG ctnIndx = pdoExt->collectionIndex;
|
|
PHIDCLASS_COLLECTION collection = &fdoExt->classCollectionArray[ctnIndx];
|
|
LIST_ENTRY dequeue, *entry;
|
|
PIRP irp;
|
|
|
|
DBGVERBOSE(("Got QUERY/SURPRISE REMOVE for collection; completing all pending reads. openCount=%d, pendingReads=%d.", pdoExt->openCount, collection->numPendingReads))
|
|
|
|
CompleteAllPendingReadsForCollection(collection);
|
|
|
|
DequeueAllPdoPowerDelayedIrps(pdoExt, &dequeue);
|
|
while (!IsListEmpty(&dequeue)) {
|
|
entry = RemoveHeadList(&dequeue);
|
|
irp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
|
|
|
|
irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
|
|
IoCompleteRequest(irp, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
|
|
case IRP_MN_CANCEL_REMOVE_DEVICE:
|
|
status = STATUS_SUCCESS;
|
|
|
|
DBGSTATE(pdoExt->state, COLLECTION_STATE_REMOVING, TRUE)
|
|
pdoExt->state = pdoExt->prevState;
|
|
if (pdoExt->state == COLLECTION_STATE_RUNNING) {
|
|
// Re-create the symbolic link, since we're no longer
|
|
// deleting the device.
|
|
HidpCreateSymbolicLink(pdoExt, pdoExt->collectionNum, TRUE, pdoExt->pdo);
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_REMOVE_DEVICE:
|
|
|
|
/*
|
|
* REMOVE_DEVICE for the device-FDO should come after REMOVE_DEVICE for each
|
|
* of the collection-PDOs.
|
|
*/
|
|
DBGASSERT((pdoExt->state == COLLECTION_STATE_UNINITIALIZED ||
|
|
pdoExt->state == COLLECTION_STATE_REMOVING),
|
|
("On pnp remove, collection state is incorrect. Actual: %x", pdoExt->state),
|
|
TRUE)
|
|
|
|
HidpRemoveCollection(fdoExt, pdoExt, Irp);
|
|
if (ISPTR(pdoExt->StatusChangeFn)) {
|
|
pdoExt->StatusChangeFn(pdoExt->StatusChangeContext,
|
|
DeviceObjectRemoved);
|
|
}
|
|
status = STATUS_SUCCESS; // Can't fail IRP_MN_REMOVE
|
|
break;
|
|
|
|
case IRP_MN_QUERY_CAPABILITIES:
|
|
status = HidpQueryCollectionCapabilities(pdoExt, Irp);
|
|
break;
|
|
|
|
|
|
case IRP_MN_QUERY_DEVICE_RELATIONS:
|
|
if (irpSp->Parameters.QueryDeviceRelations.Type == TargetDeviceRelation){
|
|
/*
|
|
* Return a reference to this PDO
|
|
*/
|
|
PDEVICE_RELATIONS devRel = ALLOCATEPOOL(PagedPool, sizeof(DEVICE_RELATIONS));
|
|
if (devRel){
|
|
/*
|
|
* Add a reference to the PDO, since CONFIGMG will free it.
|
|
*/
|
|
ObReferenceObject(pdoExt->pdo);
|
|
devRel->Objects[0] = pdoExt->pdo;
|
|
devRel->Count = 1;
|
|
Irp->IoStatus.Information = (ULONG_PTR)devRel;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Fail this Irp by returning the default
|
|
* status (typically STATUS_NOT_SUPPORTED).
|
|
*/
|
|
status = Irp->IoStatus.Status;
|
|
}
|
|
break;
|
|
|
|
case IRP_MN_QUERY_ID:
|
|
status = HidpQueryIdForClientPdo(HidDeviceExtension, Irp);
|
|
break;
|
|
|
|
case IRP_MN_QUERY_PNP_DEVICE_STATE:
|
|
//
|
|
// Do not clear any flags that may have been set by drivers above
|
|
// the PDO
|
|
//
|
|
// Irp->IoStatus.Information = 0;
|
|
|
|
switch (pdoExt->state){
|
|
case DEVICE_STATE_START_FAILURE:
|
|
Irp->IoStatus.Information |= PNP_DEVICE_FAILED;
|
|
break;
|
|
case DEVICE_STATE_STOPPED:
|
|
Irp->IoStatus.Information |= PNP_DEVICE_DISABLED;
|
|
break;
|
|
case DEVICE_STATE_REMOVING:
|
|
case DEVICE_STATE_REMOVED:
|
|
Irp->IoStatus.Information |= PNP_DEVICE_REMOVED;
|
|
break;
|
|
}
|
|
status = STATUS_SUCCESS;
|
|
break;
|
|
case IRP_MN_QUERY_INTERFACE:
|
|
status = HidpQueryInterface(HidDeviceExtension, Irp);
|
|
break;
|
|
|
|
case IRP_MN_QUERY_BUS_INFORMATION:
|
|
{
|
|
PPNP_BUS_INFORMATION busInfo = (PPNP_BUS_INFORMATION) ALLOCATEPOOL(NonPagedPool, sizeof(PNP_BUS_INFORMATION));
|
|
if (busInfo) {
|
|
busInfo->BusTypeGuid = GUID_BUS_TYPE_HID;
|
|
busInfo->LegacyBusType = PNPBus;
|
|
busInfo->BusNumber = fdoExt->BusNumber;
|
|
Irp->IoStatus.Information = (ULONG_PTR) busInfo;
|
|
status = STATUS_SUCCESS;
|
|
} else {
|
|
status = STATUS_INSUFFICIENT_RESOURCES;
|
|
Irp->IoStatus.Information = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* In the default case for the collection-PDOs we complete the IRP
|
|
* without changing IoStatus.Status; we also return the preset IoStatus.Status.
|
|
* This allows an upper filter driver to set IoStatus.Status
|
|
* on the way down. In the absence of a filter driver,
|
|
* IoStatus.Status will be STATUS_NOT_SUPPORTED.
|
|
*
|
|
* In the default case for the FDO we send the Irp on and let
|
|
* the other drivers in the stack do their thing.
|
|
*/
|
|
status = Irp->IoStatus.Status;
|
|
break;
|
|
}
|
|
|
|
|
|
/*
|
|
* If this is a call for a collection-PDO, we complete it ourselves here.
|
|
* Otherwise, we pass it to the minidriver stack for more processing.
|
|
*/
|
|
ASSERT(status != NO_STATUS);
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
DBG_LOG_PNP_IRP(Irp, minorFunction, TRUE, TRUE, status)
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
NTSTATUS HidpFdoPnp(
|
|
IN PHIDCLASS_DEVICE_EXTENSION HidDeviceExtension,
|
|
IN OUT PIRP Irp
|
|
)
|
|
{
|
|
NTSTATUS status = NO_STATUS;
|
|
PIO_STACK_LOCATION irpSp;
|
|
FDO_EXTENSION *fdoExt;
|
|
BOOLEAN completeIrpHere = FALSE; // general rule
|
|
UCHAR minorFunction;
|
|
|
|
PAGED_CODE();
|
|
|
|
irpSp = IoGetCurrentIrpStackLocation(Irp);
|
|
|
|
/*
|
|
* Keep these fields privately so that we have them
|
|
* after the IRP is completed and in case we delete
|
|
* the device extension on a REMOVE_DEVICE.
|
|
*/
|
|
minorFunction = irpSp->MinorFunction;
|
|
|
|
|
|
DBG_LOG_PNP_IRP(Irp, minorFunction, FALSE, FALSE, 0)
|
|
|
|
fdoExt = &HidDeviceExtension->fdoExt;
|
|
|
|
switch (minorFunction){
|
|
|
|
case IRP_MN_START_DEVICE:
|
|
|
|
status = HidpStartDevice(HidDeviceExtension, Irp);
|
|
completeIrpHere = TRUE;
|
|
break;
|
|
|
|
case IRP_MN_QUERY_STOP_DEVICE:
|
|
/*
|
|
* We will pass this IRP down the driver stack.
|
|
* However, we need to change the default status
|
|
* from STATUS_NOT_SUPPORTED to STATUS_SUCCESS.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
DBGSTATE(fdoExt->state, DEVICE_STATE_START_SUCCESS, FALSE)
|
|
fdoExt->prevState = fdoExt->state;
|
|
fdoExt->state = DEVICE_STATE_STOPPING;
|
|
break;
|
|
|
|
case IRP_MN_CANCEL_STOP_DEVICE:
|
|
/*
|
|
* We will pass this IRP down the driver stack.
|
|
* However, we need to change the default status
|
|
* from STATUS_NOT_SUPPORTED to STATUS_SUCCESS.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
DBGSTATE(fdoExt->state, DEVICE_STATE_STOPPING, TRUE)
|
|
fdoExt->state = fdoExt->prevState;
|
|
break;
|
|
|
|
case IRP_MN_STOP_DEVICE:
|
|
DBGSTATE(fdoExt->state, DEVICE_STATE_STOPPING, TRUE)
|
|
if (fdoExt->prevState == DEVICE_STATE_START_SUCCESS){
|
|
|
|
/*
|
|
* While it is stopped, the host controller may not be able
|
|
* to complete IRPs. So cancel them before sending down the stop.
|
|
*/
|
|
CancelAllPingPongIrps(fdoExt);
|
|
}
|
|
fdoExt->state = DEVICE_STATE_STOPPED;
|
|
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
status = HidpCallDriverSynchronous(fdoExt->fdo, Irp);
|
|
|
|
completeIrpHere = TRUE;
|
|
break;
|
|
|
|
case IRP_MN_SURPRISE_REMOVAL:
|
|
//
|
|
// On surprise removal, we should stop accessing the device.
|
|
// We do the same steps on IRP_MN_REMOVE_DEVICE for the query
|
|
// removal case. Don't bother doing it during query remove,
|
|
// itself, because we don't want to have to handle the
|
|
// cancel case. NOTE: We only get away with this because all
|
|
// of these steps can be repeated without dire consequences.
|
|
//
|
|
if (ISPTR(fdoExt->waitWakeIrp)){
|
|
IoCancelIrp(fdoExt->waitWakeIrp);
|
|
fdoExt->waitWakeIrp = BAD_POINTER;
|
|
}
|
|
|
|
HidpCancelIdleNotification(fdoExt, TRUE);
|
|
|
|
if (ISPTR(fdoExt->idleNotificationRequest)) {
|
|
IoFreeIrp(fdoExt->idleNotificationRequest);
|
|
fdoExt->idleNotificationRequest = BAD_POINTER;
|
|
}
|
|
|
|
DestroyPingPongs(fdoExt);
|
|
|
|
// fall thru to IRP_MN_QUERY_REMOVE_DEVICE
|
|
|
|
case IRP_MN_QUERY_REMOVE_DEVICE:
|
|
{
|
|
PIRP idleIrp;
|
|
|
|
while (idleIrp = DequeuePowerDelayedIrp(fdoExt)) {
|
|
idleIrp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
|
|
IoCompleteRequest(idleIrp, IO_NO_INCREMENT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We will pass this IRP down the driver stack.
|
|
* However, we need to change the default status
|
|
* from STATUS_NOT_SUPPORTED to STATUS_SUCCESS.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
DBGSTATE(fdoExt->state, DEVICE_STATE_START_SUCCESS, FALSE)
|
|
DBGASSERT((fdoExt->state == DEVICE_STATE_START_SUCCESS ||
|
|
fdoExt->state == DEVICE_STATE_STOPPED),
|
|
("Fdo is neither stopped nor started, but is getting removed, state=%d",fdoExt->state),
|
|
FALSE)
|
|
fdoExt->prevState = fdoExt->state;
|
|
fdoExt->state = DEVICE_STATE_REMOVING;
|
|
break;
|
|
|
|
case IRP_MN_CANCEL_REMOVE_DEVICE:
|
|
/*
|
|
* We will pass this IRP down the driver stack.
|
|
* However, we need to change the default status
|
|
* from STATUS_NOT_SUPPORTED to STATUS_SUCCESS.
|
|
*/
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
|
|
DBGSTATE(fdoExt->state, DEVICE_STATE_REMOVING, TRUE)
|
|
fdoExt->state = fdoExt->prevState;
|
|
break;
|
|
|
|
case IRP_MN_REMOVE_DEVICE:
|
|
|
|
/*
|
|
* REMOVE_DEVICE for the device-FDO should come after REMOVE_DEVICE
|
|
* for each of the collection-PDOs.
|
|
* Don't touch the device extension after this call.
|
|
*/
|
|
DBGASSERT((fdoExt->state == DEVICE_STATE_REMOVING ||
|
|
fdoExt->state == DEVICE_STATE_START_FAILURE ||
|
|
fdoExt->state == DEVICE_STATE_INITIALIZED),
|
|
("Incorrect device state: %x", fdoExt->state),
|
|
TRUE)
|
|
status = HidpRemoveDevice(fdoExt, Irp);
|
|
goto HidpFdoPnpDone;
|
|
break;
|
|
|
|
case IRP_MN_QUERY_DEVICE_RELATIONS:
|
|
if (irpSp->Parameters.QueryDeviceRelations.Type == BusRelations){
|
|
status = HidpQueryDeviceRelations(HidDeviceExtension, Irp);
|
|
if (NT_SUCCESS(status)){
|
|
/*
|
|
* Although we have satisfied this PnP IRP,
|
|
* we will still pass it down the stack.
|
|
* First change the default status to our status.
|
|
*/
|
|
Irp->IoStatus.Status = status;
|
|
}
|
|
else {
|
|
completeIrpHere = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
/*
|
|
* In the default case for the collection-PDOs we complete the IRP
|
|
* without changing IoStatus.Status; we also return the preset IoStatus.Status.
|
|
* This allows an upper filter driver to set IoStatus.Status
|
|
* on the way down. In the absence of a filter driver,
|
|
* IoStatus.Status will be STATUS_NOT_SUPPORTED.
|
|
*
|
|
* In the default case for the FDO we send the Irp on and let
|
|
* the other drivers in the stack do their thing.
|
|
*/
|
|
if (completeIrpHere){
|
|
status = Irp->IoStatus.Status;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
/*
|
|
* If this is a call for a collection-PDO, we complete it ourselves here.
|
|
* Otherwise, we pass it to the minidriver stack for more processing.
|
|
*/
|
|
if (completeIrpHere){
|
|
ASSERT(status != NO_STATUS);
|
|
Irp->IoStatus.Status = status;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
else {
|
|
/*
|
|
* Call the minidriver with this Irp.
|
|
* The rest of our processing will be done in our completion routine.
|
|
*
|
|
* Note: Don't touch the Irp after sending it down, since it may
|
|
* be completed immediately.
|
|
*/
|
|
IoCopyCurrentIrpStackLocationToNext(Irp);
|
|
status = HidpCallDriver(fdoExt->fdo, Irp);
|
|
}
|
|
|
|
HidpFdoPnpDone:
|
|
DBG_LOG_PNP_IRP(Irp, minorFunction, FALSE, TRUE, status)
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|