windows-nt/Source/XPSP1/NT/drivers/wdm/input/hidclass/util.c

2511 lines
80 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1996 Microsoft Corporation
Module Name:
util.c
Abstract
Internal utility functions for the HID class driver.
Authors:
Ervin P.
Environment:
Kernel mode only
Revision History:
--*/
#include "pch.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, HidpAddDevice)
#pragma alloc_text(PAGE, HidpDriverUnload)
#pragma alloc_text(PAGE, HidpGetDeviceDescriptor)
#pragma alloc_text(PAGE, HidpQueryDeviceCapabilities)
#pragma alloc_text(PAGE, HidpQueryIdForClientPdo)
#pragma alloc_text(PAGE, SubstituteBusNames)
#pragma alloc_text(PAGE, BuildCompatibleID)
#pragma alloc_text(PAGE, HidpQueryCollectionCapabilities)
#pragma alloc_text(PAGE, HidpQueryDeviceRelations)
#pragma alloc_text(PAGE, HidpCreateClientPDOs)
#pragma alloc_text(PAGE, MakeClientPDOName)
#pragma alloc_text(PAGE, HidpCreateSymbolicLink)
#pragma alloc_text(PAGE, HidpQueryInterface)
#endif
/*
********************************************************************************
* HidpCopyInputReportToUser
********************************************************************************
*
* Copy a read report into a user's buffer.
*
* Note: ReportData is already "cooked" (already has report-id byte at start of report).
*
*/
NTSTATUS HidpCopyInputReportToUser(
IN PHIDCLASS_FILE_EXTENSION FileExtension,
IN PUCHAR ReportData,
IN OUT PULONG UserBufferLen,
OUT PUCHAR UserBuffer
)
{
NTSTATUS result = STATUS_DEVICE_DATA_ERROR;
ULONG reportId;
PHIDP_REPORT_IDS reportIdentifier;
FDO_EXTENSION *fdoExtension = FileExtension->fdoExt;
RUNNING_DISPATCH();
ASSERT(fdoExtension->deviceDesc.CollectionDescLength > 0);
reportId = (ULONG)*ReportData;
reportIdentifier = GetReportIdentifier(fdoExtension, reportId);
if (reportIdentifier){
PHIDP_COLLECTION_DESC collectionDesc;
PHIDCLASS_COLLECTION hidpCollection;
collectionDesc = GetCollectionDesc(fdoExtension, reportIdentifier->CollectionNumber);
hidpCollection = GetHidclassCollection(fdoExtension, reportIdentifier->CollectionNumber);
if (collectionDesc && hidpCollection){
ULONG reportLength = collectionDesc->InputLength;
if (*UserBufferLen >= reportLength){
RtlCopyMemory(UserBuffer, ReportData, reportLength);
result = STATUS_SUCCESS;
}
else {
result = STATUS_INVALID_BUFFER_SIZE;
}
/*
* Return the actual length of the report (whether we copied or not).
*/
*UserBufferLen = reportLength;
}
}
ASSERT((result == STATUS_SUCCESS) || (result == STATUS_INVALID_BUFFER_SIZE));
return result;
}
/*
********************************************************************************
* HidpAddDevice
********************************************************************************
*
* Routine Description:
*
* This routine is called by configmgr when a new PDO is dected.
* It creates an Functional Device Object (FDO) and attaches it to the
* PDO.
*
* Arguments:
*
* DriverObject - pointer to the minidriver's driver object.
*
* PhysicalDeviceObject - pointer to the PDO that the minidriver got in it's
* AddDevice() routine.
*
* Return Value:
*
* Standard NT return value.
*
*/
NTSTATUS HidpAddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject)
{
PHIDCLASS_DRIVER_EXTENSION hidDriverExtension;
PHIDCLASS_DEVICE_EXTENSION hidClassExtension;
NTSTATUS status;
UNICODE_STRING uPdoName;
PWSTR wPdoName;
PVOID miniDeviceExtension;
ULONG totalExtensionSize;
ULONG thisHidId;
PDEVICE_OBJECT functionalDeviceObject;
PAGED_CODE();
DBG_COMMON_ENTRY()
DBG_RECORD_DEVOBJ(PhysicalDeviceObject, "minidrvr PDO")
//
// Get a pointer to our per-driver extension, make sure it's one of ours.
//
hidDriverExtension = RefDriverExt(DriverObject);
if (hidDriverExtension){
ASSERT(DriverObject == hidDriverExtension->MinidriverObject);
//
// Construct a name for the FDO. The only requirement, really, is
// that it's unique. For now we'll call them "_HIDx", where 'x' is some
// unique number.
//
/*
* PDO name has form "\Device\_HIDx".
*/
wPdoName = ALLOCATEPOOL(NonPagedPool, sizeof(L"\\Device\\_HID00000000"));
if (wPdoName){
//
// Get the current value of NextHidId and increment it. Since
// InterlockedIncrement() returns the incremented value, subtract one to
// get the pre-increment value of NextHidId;
//
thisHidId = InterlockedIncrement(&HidpNextHidNumber) - 1;
swprintf(wPdoName, L"\\Device\\_HID%08x", thisHidId);
RtlInitUnicodeString(&uPdoName, wPdoName);
//
// We've got a counted-string version of the device object name. Calculate
// the total size of the device extension and create the FDO.
//
totalExtensionSize = sizeof(HIDCLASS_DEVICE_EXTENSION) +
hidDriverExtension->DeviceExtensionSize;
status = IoCreateDevice( DriverObject, // driver object
totalExtensionSize, // extension size
&uPdoName, // name of the FDO
FILE_DEVICE_UNKNOWN, // what the hell
0, // DeviceCharacteristics
FALSE, // not exclusive
&functionalDeviceObject );
if (NT_SUCCESS(status)){
DBG_RECORD_DEVOBJ(functionalDeviceObject, "device FDO")
ObReferenceObject(functionalDeviceObject);
ASSERT(DriverObject->DeviceObject == functionalDeviceObject);
ASSERT(functionalDeviceObject->DriverObject == DriverObject);
//
// We've created the device object. Fill in the minidriver's extension
// pointer and attach this FDO to the PDO.
//
hidClassExtension = functionalDeviceObject->DeviceExtension;
RtlZeroMemory(hidClassExtension, totalExtensionSize);
hidClassExtension->isClientPdo = FALSE;
//
// Assign the name of the minidriver's PDO to our FDO.
//
hidClassExtension->fdoExt.name = uPdoName;
//
// The minidriver extension lives in the device extension and starts
// immediately after our HIDCLASS_DEVICE_EXTENSION structure. Note
// that the first structure in the HIDCLASS_DEVICE_EXTENSION is the
// public HID_DEVICE_EXTENSION structure, which is where the pointer
// to the minidriver's per-device extension area lives.
//
miniDeviceExtension = (PVOID)(hidClassExtension + 1);
hidClassExtension->hidExt.MiniDeviceExtension = miniDeviceExtension;
//
// Get a pointer to the physical device object passed in. This device
// object should already have the DO_DEVICE_INITIALIZING flag cleared.
//
ASSERT( (PhysicalDeviceObject->Flags & DO_DEVICE_INITIALIZING) == 0 );
//
// Attach the FDO to the PDO, storing the device object at the top of the
// stack in our device extension.
//
hidClassExtension->hidExt.NextDeviceObject =
IoAttachDeviceToDeviceStack( functionalDeviceObject,
PhysicalDeviceObject );
ASSERT(DriverObject->DeviceObject == functionalDeviceObject);
ASSERT(functionalDeviceObject->DriverObject == DriverObject);
//
// The functional device requires two stack locations: one for the class
// driver, and one for the minidriver.
//
functionalDeviceObject->StackSize++;
//
// We'll need a pointer to the physical device object as well for PnP
// purposes. Note that it's a virtual certainty that NextDeviceObject
// and PhysicalDeviceObject are identical.
//
hidClassExtension->hidExt.PhysicalDeviceObject = PhysicalDeviceObject;
hidClassExtension->Signature = HID_DEVICE_EXTENSION_SIG;
hidClassExtension->fdoExt.fdo = functionalDeviceObject;
hidClassExtension->fdoExt.driverExt = hidDriverExtension;
hidClassExtension->fdoExt.outstandingRequests = 0;
hidClassExtension->fdoExt.openCount = 0;
hidClassExtension->fdoExt.state = DEVICE_STATE_INITIALIZED;
//
// Selective suspend portion.
//
hidClassExtension->fdoExt.idleState = IdleDisabled;
hidClassExtension->fdoExt.idleTimeoutValue = BAD_POINTER;
KeInitializeSpinLock(&hidClassExtension->fdoExt.idleNotificationSpinLock);
KeInitializeEvent(&hidClassExtension->fdoExt.idleDoneEvent, NotificationEvent, TRUE);
hidClassExtension->fdoExt.idleNotificationRequest = BAD_POINTER;
hidClassExtension->fdoExt.idleCallbackInfo.IdleCallback = HidpIdleNotificationCallback;
hidClassExtension->fdoExt.idleCallbackInfo.IdleContext = (PVOID) hidClassExtension;
hidClassExtension->fdoExt.systemPowerState = PowerSystemWorking;
hidClassExtension->fdoExt.devicePowerState = PowerDeviceD0;
hidClassExtension->fdoExt.waitWakeIrp = BAD_POINTER;
KeInitializeSpinLock(&hidClassExtension->fdoExt.waitWakeSpinLock);
hidClassExtension->fdoExt.isWaitWakePending = FALSE;
InitializeListHead(&hidClassExtension->fdoExt.collectionWaitWakeIrpQueue);
KeInitializeSpinLock(&hidClassExtension->fdoExt.collectionWaitWakeIrpQueueSpinLock);
InitializeListHead(&hidClassExtension->fdoExt.collectionPowerDelayedIrpQueue);
KeInitializeSpinLock(&hidClassExtension->fdoExt.collectionPowerDelayedIrpQueueSpinLock);
hidClassExtension->fdoExt.numPendingPowerDelayedIrps = 0;
hidClassExtension->fdoExt.BusNumber = thisHidId;
#if DBG
InitFdoExtDebugInfo(hidClassExtension);
#endif
EnqueueFdoExt(&hidClassExtension->fdoExt);
/*
* Indicate that this device object does direct I/O.
*
* Set the flag that causes the IO subsystem to decrement the device
* object's reference count *before* sending down IRP_MJ_CLOSEs. We
* need this because we delete the device object on the last close.
*/
functionalDeviceObject->Flags |= DO_DIRECT_IO;
/*
* The DO_POWER_PAGABLE bit of a device object
* indicates to the kernel that the power-handling
* code of the corresponding driver is pageable, and
* so must be called at IRQL 0.
* As a filter driver, we do not want to change the power
* behavior of the driver stack in any way; therefore,
* we copy this bit from the lower device object.
*/
functionalDeviceObject->Flags |= (PhysicalDeviceObject->Flags & DO_POWER_PAGABLE);
/*
* Must clear the initializing flag after initialization complete.
*/
functionalDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
ReadDeviceFlagsFromRegistry(&hidClassExtension->fdoExt,
PhysicalDeviceObject);
//
// Since we have NOT seen a start device, we CANNOT send any non
// pnp irps to the device yet. We need to do that in the start
// device requests.
//
//
// Call the minidriver to let it do any extension initialization
//
status = hidDriverExtension->AddDevice(DriverObject, functionalDeviceObject);
if (!NT_SUCCESS(status)) {
DequeueFdoExt(&hidClassExtension->fdoExt);
IoDetachDevice(hidClassExtension->hidExt.NextDeviceObject);
ObDereferenceObject(functionalDeviceObject);
IoDeleteDevice(functionalDeviceObject);
ExFreePool( wPdoName );
}
}
else {
TRAP;
ExFreePool( wPdoName );
}
}
else {
TRAP;
status = STATUS_INSUFFICIENT_RESOURCES;
}
if (!NT_SUCCESS(status)){
DerefDriverExt(DriverObject);
}
}
else {
ASSERT(hidDriverExtension);
status = STATUS_DEVICE_CONFIGURATION_ERROR;
}
DBGSUCCESS(status, TRUE)
DBG_COMMON_EXIT()
return status;
}
/*
********************************************************************************
* HidpDriverUnload
********************************************************************************
*
*
*/
VOID HidpDriverUnload(IN struct _DRIVER_OBJECT *minidriverObject)
{
PHIDCLASS_DRIVER_EXTENSION hidDriverExt;
PAGED_CODE();
DBG_COMMON_ENTRY()
/*
* This extra de-reference will cause our hidDriverExtension's
* reference count to eventually go to -1; at that time, we'll
* dequeue it.
*/
hidDriverExt = DerefDriverExt(minidriverObject);
ASSERT(hidDriverExt);
/*
* Chain the unload call to the minidriver.
*/
hidDriverExt->DriverUnload(minidriverObject);
DBG_COMMON_EXIT()
}
NTSTATUS GetHIDRawReportDescriptor(FDO_EXTENSION *fdoExt, PIRP irp, ULONG descriptorLen)
{
NTSTATUS status;
if (descriptorLen){
PUCHAR rawReportDescriptor = ALLOCATEPOOL(NonPagedPool, descriptorLen);
if (rawReportDescriptor){
const ULONG retries = 3;
ULONG i;
for (i = 0; i < retries; i++){
PIO_STACK_LOCATION irpSp;
irp->UserBuffer = rawReportDescriptor;
irpSp = IoGetNextIrpStackLocation(irp);
ASSERT(irpSp->Parameters.DeviceIoControl.InputBufferLength == 0);
ASSERT(irpSp->Parameters.DeviceIoControl.Type3InputBuffer == NULL);
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
irpSp->Parameters.DeviceIoControl.OutputBufferLength = descriptorLen;
irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_HID_GET_REPORT_DESCRIPTOR;
//
// Call the minidriver to get the report descriptor.
//
status = HidpCallDriverSynchronous(fdoExt->fdo, irp);
if (NT_SUCCESS(status)){
if (irp->IoStatus.Information == descriptorLen){
fdoExt->rawReportDescriptionLength = descriptorLen;
fdoExt->rawReportDescription = rawReportDescriptor;
break;
} else {
DBGWARN(("GetHIDRawReportDescriptor (attempt #%d) returned %xh/%xh bytes", i, irp->IoStatus.Information, descriptorLen))
status = STATUS_DEVICE_DATA_ERROR;
}
} else {
DBGWARN(("GetHIDRawReportDescriptor (attempt #%d) failed with status %xh.", i, status))
}
}
if (!NT_SUCCESS(status)){
DBGWARN(("GetHIDRawReportDescriptor failed %d times.", retries))
ExFreePool(rawReportDescriptor);
}
} else {
DBGWARN(("alloc failed in GetHIDRawReportDescriptor"))
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
else {
DBGWARN(("GetHIDRawReportDescriptor: descriptorLen is zero."))
status = STATUS_DEVICE_DATA_ERROR;
}
DBGSUCCESS(status, FALSE)
return status;
}
/*
********************************************************************************
* HidpGetDeviceDescriptor
********************************************************************************
*
*
*/
NTSTATUS HidpGetDeviceDescriptor(FDO_EXTENSION *fdoExtension)
{
PIRP irp;
PIO_STACK_LOCATION irpSp;
NTSTATUS status;
PHID_DESCRIPTOR hidDescriptor;
ULONG rawReportDescriptorLength;
PAGED_CODE();
/*
* Retrieve:
*
* 1. Device descriptor (fixed portion)
* 2. Device attributes
* 3. Report descriptor
*/
hidDescriptor = &fdoExtension->hidDescriptor;
irp = IoAllocateIrp(fdoExtension->fdo->StackSize, FALSE);
if (irp){
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_HID_GET_DEVICE_DESCRIPTOR;
/*
* This IOCTL uses buffering type METHOD_NEITHER, so
* the buffer is simply passed in irp->UserBuffer.
*/
irp->UserBuffer = hidDescriptor;
irpSp->Parameters.DeviceIoControl.OutputBufferLength = sizeof(HID_DESCRIPTOR);
irpSp->Parameters.DeviceIoControl.Type3InputBuffer = NULL;
status = HidpCallDriverSynchronous(fdoExtension->fdo, irp);
DBGASSERT((status == STATUS_SUCCESS),
("STATUS_SUCCESS not returned, %x returned",status),
TRUE)
if (status == STATUS_SUCCESS){
if (irp->IoStatus.Information == sizeof(HID_DESCRIPTOR)){
irpSp = IoGetNextIrpStackLocation(irp);
ASSERT(irpSp->MajorFunction == IRP_MJ_INTERNAL_DEVICE_CONTROL);
ASSERT(irpSp->Parameters.DeviceIoControl.InputBufferLength == 0);
ASSERT(!irpSp->Parameters.DeviceIoControl.Type3InputBuffer);
irpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_HID_GET_DEVICE_ATTRIBUTES;
irp->UserBuffer = &fdoExtension->hidDeviceAttributes;
irpSp->Parameters.DeviceIoControl.OutputBufferLength = sizeof(HID_DEVICE_ATTRIBUTES);
status = HidpCallDriverSynchronous(fdoExtension->fdo, irp);
DBGASSERT((status == STATUS_SUCCESS),
("STATUS_SUCCESS not returned, %x returned",status),
TRUE)
if (NT_SUCCESS (status)) {
ULONG i;
/*
* We've got a hid descriptor, now we need to read the report descriptor.
*
* Find the descriptor describing the report.
*/
rawReportDescriptorLength = 0;
for (i = 0; i < hidDescriptor->bNumDescriptors; i++){
if (hidDescriptor->DescriptorList[i].bReportType == HID_REPORT_DESCRIPTOR_TYPE){
rawReportDescriptorLength = (ULONG)hidDescriptor->DescriptorList[i].wReportLength;
break;
}
}
status = GetHIDRawReportDescriptor(fdoExtension, irp, rawReportDescriptorLength);
}
else {
status = STATUS_DEVICE_DATA_ERROR;
}
}
else {
status = STATUS_DEVICE_DATA_ERROR;
}
}
else {
status = STATUS_DEVICE_DATA_ERROR;
}
IoFreeIrp(irp);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
DBGSUCCESS(status, FALSE)
return status;
}
/*
********************************************************************************
* HidpCreateSymbolicLink
********************************************************************************
*
*
*/
NTSTATUS HidpCreateSymbolicLink(
IN PDO_EXTENSION *pdoExt,
IN ULONG collectionNum,
IN BOOLEAN Create,
IN PDEVICE_OBJECT Pdo
)
{
NTSTATUS status;
PHIDCLASS_COLLECTION classCollection;
PAGED_CODE();
classCollection = GetHidclassCollection(&pdoExt->deviceFdoExt->fdoExt, collectionNum);
if (classCollection){
//
// We've got a collection. Figure out what it is and create a symbolic
// link for it. For now we assign the "input" guid to all hid devices.
// The reference string is simply the collection number, zero-padded
// to eight digits.
//
if (Create){
/*
* Mark the PDO as initialized
*/
Pdo->Flags |= DO_DIRECT_IO;
Pdo->Flags &= ~DO_DEVICE_INITIALIZING;
/*
* Create the symbolic link
*/
status = IoRegisterDeviceInterface(
Pdo,
(LPGUID)&GUID_CLASS_INPUT,
NULL,
&classCollection->SymbolicLinkName );
if (NT_SUCCESS(status)){
/*
* Now set the symbolic link for the association and store it..
*/
ASSERT(ISPTR(pdoExt->name));
status = IoSetDeviceInterfaceState(&classCollection->SymbolicLinkName, TRUE);
}
}
else {
/*
* Disable the symbolic link
*/
if (ISPTR(classCollection->SymbolicLinkName.Buffer)){
status = IoSetDeviceInterfaceState(&classCollection->SymbolicLinkName, FALSE);
ExFreePool( classCollection->SymbolicLinkName.Buffer );
classCollection->SymbolicLinkName.Buffer = BAD_POINTER;
}
else {
status = STATUS_SUCCESS;
}
}
}
else {
status = STATUS_DEVICE_CONFIGURATION_ERROR;
}
DBGSUCCESS(status, TRUE)
return status;
}
/*
********************************************************************************
* EnqueueInterruptReport
********************************************************************************
*
*
*/
VOID EnqueueInterruptReport(PHIDCLASS_FILE_EXTENSION fileExtension,
PHIDCLASS_REPORT report)
{
PHIDCLASS_REPORT reportToDrop = NULL;
RUNNING_DISPATCH();
/*
* If the queue is full, drop the oldest report.
*/
if (fileExtension->CurrentInputReportQueueSize >= fileExtension->MaximumInputReportQueueSize){
PLIST_ENTRY listEntry;
#if DBG
if (fileExtension->dbgNumReportsDroppedSinceLastRead++ == 0){
DBGWARN(("HIDCLASS dropping input reports because report queue (size %xh) is full ...", fileExtension->MaximumInputReportQueueSize))
DBGASSERT((fileExtension->CurrentInputReportQueueSize == fileExtension->MaximumInputReportQueueSize),
("Current report queue size (%xh) is greater than maximum (%xh)",
fileExtension->CurrentInputReportQueueSize,
fileExtension->MaximumInputReportQueueSize),
FALSE);
}
#endif
ASSERT(!IsListEmpty(&fileExtension->ReportList));
listEntry = RemoveHeadList(&fileExtension->ReportList);
reportToDrop = CONTAINING_RECORD(listEntry, HIDCLASS_REPORT, ListEntry);
fileExtension->CurrentInputReportQueueSize--;
}
/*
* Now queue the current report
*/
InsertTailList(&fileExtension->ReportList, &report->ListEntry);
fileExtension->CurrentInputReportQueueSize++;
/*
* We don't have to be running < DPC_LEVEL to release reports since they
* are allocated using NonPagePool.
*/
if (reportToDrop){
ExFreePool(reportToDrop);
}
}
/*
********************************************************************************
* DequeueInterruptReport
********************************************************************************
*
* Return the next interrupt report in the queue.
* If maxLen is not -1, then only return the report if it is <= maxlen.
*/
PHIDCLASS_REPORT DequeueInterruptReport(PHIDCLASS_FILE_EXTENSION fileExtension,
LONG maxLen)
{
PHIDCLASS_REPORT report;
RUNNING_DISPATCH();
if (IsListEmpty(&fileExtension->ReportList)){
report = NULL;
}
else {
PLIST_ENTRY listEntry = RemoveHeadList(&fileExtension->ReportList);
report = CONTAINING_RECORD(listEntry, HIDCLASS_REPORT, ListEntry);
if ((maxLen > 0) && (report->reportLength > (ULONG)maxLen)){
/*
* This report is too big for the caller.
* So put the report back in the queue and return NULL.
*/
InsertHeadList(&fileExtension->ReportList, &report->ListEntry);
report = NULL;
}
else {
InitializeListHead(&report->ListEntry);
ASSERT(fileExtension->CurrentInputReportQueueSize > 0);
fileExtension->CurrentInputReportQueueSize--;
#if DBG
if (fileExtension->dbgNumReportsDroppedSinceLastRead > 0){
DBGWARN(("... successful read(/flush) after %d reports were dropped.", fileExtension->dbgNumReportsDroppedSinceLastRead));
fileExtension->dbgNumReportsDroppedSinceLastRead = 0;
}
#endif
}
}
return report;
}
/*
********************************************************************************
* HidpDestroyFileExtension
********************************************************************************
*
*
*/
VOID HidpDestroyFileExtension(PHIDCLASS_COLLECTION collection, PHIDCLASS_FILE_EXTENSION FileExtension)
{
PFILE_OBJECT fileObject;
//
// Flush all of the pending reports on the file extension
//
HidpFlushReportQueue(FileExtension);
/*
* Fail all the pending reads
* (it would be nice if apps always cancelled all their reads
* before closing the device, but this is not always the case).
*/
CompleteAllPendingReadsForFileExtension(collection, FileExtension);
//
// Indicate in the file object that this file extension has gone away.
//
fileObject = FileExtension->FileObject;
#if DBG
fileObject->FsContext = NULL;
#endif
//
// Free our extension
//
#if DBG
FileExtension->Signature = ~HIDCLASS_FILE_EXTENSION_SIG;
#endif
ExFreePool( FileExtension );
}
/*
********************************************************************************
* HidpFlushReportQueue
********************************************************************************
*
*
*/
VOID HidpFlushReportQueue(IN PHIDCLASS_FILE_EXTENSION fileExtension)
{
PHIDCLASS_REPORT report;
KIRQL oldIrql;
LockFileExtension(fileExtension, &oldIrql);
while (report = DequeueInterruptReport(fileExtension, -1)){
//
// Ok to call this at DISPATCH_LEVEL, since report is NonPagedPool
//
ExFreePool(report);
}
UnlockFileExtension(fileExtension, oldIrql);
}
/*
********************************************************************************
* HidpGetCollectionInformation
********************************************************************************
*
*
*/
NTSTATUS HidpGetCollectionInformation(
IN FDO_EXTENSION *fdoExtension,
IN ULONG collectionNumber,
IN PVOID Buffer,
IN OUT PULONG BufferSize
)
{
HID_COLLECTION_INFORMATION hidCollectionInfo;
PHIDP_COLLECTION_DESC hidCollectionDesc;
ULONG bytesToCopy;
NTSTATUS status;
/*
* Get a pointer to the appropriate collection descriptor.
*/
hidCollectionDesc = GetCollectionDesc(fdoExtension, collectionNumber);
if (hidCollectionDesc){
//
// Fill in hidCollectionInfo
//
hidCollectionInfo.DescriptorSize = hidCollectionDesc->PreparsedDataLength;
hidCollectionInfo.Polled = fdoExtension->driverExt->DevicesArePolled;
hidCollectionInfo.VendorID = fdoExtension->hidDeviceAttributes.VendorID;
hidCollectionInfo.ProductID = fdoExtension->hidDeviceAttributes.ProductID;
hidCollectionInfo.VersionNumber = fdoExtension->hidDeviceAttributes.VersionNumber;
//
// Copy as much of hidCollectionInfo as will fit in the output buffer.
//
if (*BufferSize < sizeof( HID_COLLECTION_INFORMATION)){
/*
* The user's buffer is not big enough.
* We'll return the size that the buffer needs to be.
* Must return this with a real error code (not a warning)
* so that IO post-processing does not copy into (and past)
* the user's buffer.
*/
bytesToCopy = *BufferSize;
status = STATUS_INVALID_BUFFER_SIZE;
}
else {
bytesToCopy = sizeof( HID_COLLECTION_INFORMATION );
status = STATUS_SUCCESS;
}
RtlCopyMemory(Buffer, &hidCollectionInfo, bytesToCopy);
*BufferSize = sizeof (HID_COLLECTION_INFORMATION);
}
else {
status = STATUS_DATA_ERROR;
}
DBGSUCCESS(status, TRUE)
return status;
}
/*
********************************************************************************
* HidpGetCollectionDescriptor
********************************************************************************
*
*
*/
NTSTATUS HidpGetCollectionDescriptor( IN FDO_EXTENSION *fdoExtension,
IN ULONG collectionId,
IN PVOID Buffer,
IN OUT PULONG BufferSize)
{
PHIDP_COLLECTION_DESC hidCollectionDesc;
ULONG bytesToCopy;
NTSTATUS status;
hidCollectionDesc = GetCollectionDesc(fdoExtension, collectionId);
if (hidCollectionDesc){
/*
* Copy as much of the preparsed data as will fit in the output buffer.
*/
if (*BufferSize < hidCollectionDesc->PreparsedDataLength){
/*
* The user's buffer is not big enough for all the
* preparsed data.
* We'll return the size that the buffer needs to be.
* Must return this with a real error code (not a warning)
* so that IO post-processing does not copy into (and past)
* the user's buffer.
*/
bytesToCopy = *BufferSize;
status = STATUS_INVALID_BUFFER_SIZE;
}
else {
bytesToCopy = hidCollectionDesc->PreparsedDataLength;
status = STATUS_SUCCESS;
}
RtlCopyMemory(Buffer, hidCollectionDesc->PreparsedData, bytesToCopy);
*BufferSize = hidCollectionDesc->PreparsedDataLength;
}
else {
status = STATUS_DATA_ERROR;
}
DBGSUCCESS(status, TRUE)
return status;
}
/*
********************************************************************************
* GetReportIdentifier
********************************************************************************
*
*
*/
PHIDP_REPORT_IDS GetReportIdentifier(FDO_EXTENSION *fdoExtension, ULONG reportId)
{
PHIDP_REPORT_IDS result = NULL;
PHIDP_DEVICE_DESC deviceDesc = &fdoExtension->deviceDesc;
ULONG i;
if (deviceDesc->ReportIDs){
for (i = 0; i < deviceDesc->ReportIDsLength; i++){
if (deviceDesc->ReportIDs[i].ReportID == reportId){
result = &deviceDesc->ReportIDs[i];
break;
}
}
}
if (fdoExtension->deviceSpecificFlags & DEVICE_FLAG_ALLOW_FEATURE_ON_NON_FEATURE_COLLECTION){
/*
* This call from HidpGetSetFeature can fail because we allow
* feature access on non-feature collections.
*/
}
else {
DBGASSERT(result, ("Bogus report identifier requested %d", reportId), FALSE)
}
return result;
}
/*
********************************************************************************
* GetCollectionDesc
********************************************************************************
*
*
*/
PHIDP_COLLECTION_DESC GetCollectionDesc(FDO_EXTENSION *fdoExtension, ULONG collectionId)
{
PHIDP_COLLECTION_DESC result = NULL;
PHIDP_DEVICE_DESC deviceDesc = &fdoExtension->deviceDesc;
ULONG i;
if (deviceDesc->CollectionDesc){
for (i = 0; i < deviceDesc->CollectionDescLength; i++){
if (deviceDesc->CollectionDesc[i].CollectionNumber == collectionId){
result = &deviceDesc->CollectionDesc[i];
break;
}
}
}
ASSERT(result);
return result;
}
/*
********************************************************************************
* GetHidclassCollection
********************************************************************************
*
*/
PHIDCLASS_COLLECTION GetHidclassCollection(FDO_EXTENSION *fdoExtension, ULONG collectionId)
{
PHIDCLASS_COLLECTION result = NULL;
PHIDP_DEVICE_DESC deviceDesc = &fdoExtension->deviceDesc;
ULONG i;
if (ISPTR(fdoExtension->classCollectionArray)){
for (i = 0; i < deviceDesc->CollectionDescLength; i++){
if (fdoExtension->classCollectionArray[i].CollectionNumber == collectionId){
result = &fdoExtension->classCollectionArray[i];
break;
}
}
}
return result;
}
/*
********************************************************************************
* MakeClientPDOName
********************************************************************************
*
*
*/
PUNICODE_STRING MakeClientPDOName(PUNICODE_STRING fdoName, ULONG collectionId)
{
PUNICODE_STRING uPdoName;
PAGED_CODE();
uPdoName = (PUNICODE_STRING)ALLOCATEPOOL(NonPagedPool, sizeof(UNICODE_STRING));
if (uPdoName){
PWSTR wPdoName;
wPdoName = (PWSTR)ALLOCATEPOOL(
PagedPool,
fdoName->Length+sizeof(L"#COLLECTION0000000x"));
if (wPdoName){
swprintf(wPdoName, L"%s#COLLECTION%08x", fdoName->Buffer, collectionId);
RtlInitUnicodeString(uPdoName, wPdoName);
}
else {
ExFreePool(uPdoName);
uPdoName = NULL;
}
}
return uPdoName;
}
/*
********************************************************************************
* HidpCreateClientPDOs
********************************************************************************
*
*
*/
NTSTATUS HidpCreateClientPDOs(PHIDCLASS_DEVICE_EXTENSION hidClassExtension)
{
NTSTATUS ntStatus = STATUS_SUCCESS;
PHIDCLASS_DRIVER_EXTENSION hidDriverExtension;
FDO_EXTENSION *fdoExt;
PAGED_CODE();
ASSERT(!hidClassExtension->isClientPdo);
fdoExt = &hidClassExtension->fdoExt;
hidDriverExtension = RefDriverExt(fdoExt->driverExt->MinidriverObject);
if (hidDriverExtension){
/*
* We will create one PDO for each collection on this device.
*/
ULONG numPDOs = fdoExt->deviceDesc.CollectionDescLength;
if (numPDOs){
fdoExt->deviceRelations = (PDEVICE_RELATIONS)
ALLOCATEPOOL(NonPagedPool, sizeof(DEVICE_RELATIONS) + (numPDOs*sizeof(PDEVICE_OBJECT)));
if (fdoExt->deviceRelations){
fdoExt->collectionPdoExtensions =
ALLOCATEPOOL(NonPagedPool, numPDOs*sizeof(PHIDCLASS_DEVICE_EXTENSION));
if (fdoExt->collectionPdoExtensions){
ULONG i;
fdoExt->deviceRelations->Count = numPDOs;
for (i = 0; i < numPDOs; i++){
PUNICODE_STRING uPdoName;
ULONG totalExtensionSize;
ULONG collectionNum = fdoExt->deviceDesc.CollectionDesc[i].CollectionNumber;
PDEVICE_OBJECT newClientPdo;
/*
* Construct a name for the PDO we're about to create.
*/
uPdoName = MakeClientPDOName(&fdoExt->name, collectionNum);
if (uPdoName){
/*
* We use the same device extension for the client PDOs as for our FDO.
*/
totalExtensionSize = sizeof(HIDCLASS_DEVICE_EXTENSION) +
hidDriverExtension->DeviceExtensionSize;
/*
* Create a PDO to represent this collection.
* Since hidclass is not a real driver, it does not have a driver object;
* so just use the minidriver's driver object.
*
* NOTE - newClientPdo->NextDevice will point to this minidriver's NextDevice
*/
ntStatus = IoCreateDevice( hidDriverExtension->MinidriverObject, // driver object
totalExtensionSize, // extension size
uPdoName, // name of the PDO
FILE_DEVICE_UNKNOWN, // Device type
0, // DeviceCharacteristics
FALSE, // not exclusive
&newClientPdo);
if (NT_SUCCESS(ntStatus)){
PHIDCLASS_DEVICE_EXTENSION clientPdoExtension = newClientPdo->DeviceExtension;
USHORT usagePage = fdoExt->deviceDesc.CollectionDesc[i].UsagePage;
USHORT usage = fdoExt->deviceDesc.CollectionDesc[i].Usage;
DBG_RECORD_DEVOBJ(newClientPdo, "cltn PDO")
ObReferenceObject(newClientPdo);
/*
* We may pass Irps from the upper stack to the lower stack,
* so make sure there are enough stack locations for the IRPs
* we pass down.
*/
newClientPdo->StackSize = fdoExt->fdo->StackSize+1;
/*
* Initialize the PDO's extension
*/
RtlZeroMemory(clientPdoExtension, totalExtensionSize);
clientPdoExtension->hidExt = hidClassExtension->hidExt;
clientPdoExtension->isClientPdo = TRUE;
clientPdoExtension->Signature = HID_DEVICE_EXTENSION_SIG;
clientPdoExtension->pdoExt.collectionNum = collectionNum;
clientPdoExtension->pdoExt.collectionIndex = i;
clientPdoExtension->pdoExt.pdo = newClientPdo;
clientPdoExtension->pdoExt.state = COLLECTION_STATE_UNINITIALIZED;
clientPdoExtension->pdoExt.deviceFdoExt = hidClassExtension;
clientPdoExtension->pdoExt.StatusChangeFn = BAD_POINTER;
clientPdoExtension->pdoExt.name = uPdoName;
clientPdoExtension->pdoExt.devicePowerState = PowerDeviceD0;
clientPdoExtension->pdoExt.systemPowerState = fdoExt->systemPowerState;
clientPdoExtension->pdoExt.MouseOrKeyboard =
((usagePage == HID_USAGE_PAGE_GENERIC) &&
((usage == HID_USAGE_GENERIC_POINTER) ||
(usage == HID_USAGE_GENERIC_MOUSE) ||
(usage == HID_USAGE_GENERIC_KEYBOARD) ||
(usage == HID_USAGE_GENERIC_KEYPAD)));
IoInitializeRemoveLock (&clientPdoExtension->pdoExt.removeLock, HIDCLASS_POOL_TAG, 0, 10);
KeInitializeSpinLock (&clientPdoExtension->pdoExt.remoteWakeSpinLock);
clientPdoExtension->pdoExt.remoteWakeIrp = NULL;
/*
* Store a pointer to the new PDO in the FDO extension's deviceRelations array.
*/
fdoExt->deviceRelations->Objects[i] = newClientPdo;
/*
* Store a pointer to the PDO's extension.
*/
fdoExt->collectionPdoExtensions[i] = clientPdoExtension;
newClientPdo->Flags |= DO_POWER_PAGABLE;
newClientPdo->Flags &= ~DO_DEVICE_INITIALIZING;
}
else {
break;
}
}
else {
ntStatus = STATUS_NO_MEMORY;
}
}
if (!NT_SUCCESS(ntStatus)){
ExFreePool(fdoExt->collectionPdoExtensions);
fdoExt->collectionPdoExtensions = BAD_POINTER;
}
}
else {
ntStatus = STATUS_NO_MEMORY;
}
if (!NT_SUCCESS(ntStatus)){
ExFreePool(fdoExt->deviceRelations);
fdoExt->deviceRelations = BAD_POINTER;
}
}
else {
ntStatus = STATUS_NO_MEMORY;
}
}
else {
ASSERT(numPDOs);
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
}
DerefDriverExt(fdoExt->driverExt->MinidriverObject);
}
else {
ASSERT(hidDriverExtension);
ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
}
DBGSUCCESS(ntStatus, TRUE)
return ntStatus;
}
/*
********************************************************************************
* MemDup
********************************************************************************
*
* Return a fresh copy of the argument.
*
*/
PVOID MemDup(POOL_TYPE PoolType, PVOID dataPtr, ULONG length)
{
PVOID newPtr;
newPtr = (PVOID)ALLOCATEPOOL(PoolType, length);
if (newPtr){
RtlCopyMemory(newPtr, dataPtr, length);
}
ASSERT(newPtr);
return newPtr;
}
/*
********************************************************************************
* WStrLen
********************************************************************************
*
*/
ULONG WStrLen(PWCHAR str)
{
ULONG result = 0;
while (*str++ != UNICODE_NULL){
result++;
}
return result;
}
/*
********************************************************************************
* WStrCpy
********************************************************************************
*
*/
ULONG WStrCpy(PWCHAR dest, PWCHAR src)
{
ULONG result = 0;
while (*dest++ = *src++){
result++;
}
return result;
}
BOOLEAN WStrCompareN(PWCHAR str1, PWCHAR str2, ULONG maxChars)
{
while ((maxChars > 0) && *str1 && (*str1 == *str2)){
maxChars--;
str1++;
str2++;
}
return (BOOLEAN)((maxChars == 0) || (!*str1 && !*str2));
}
/*
********************************************************************************
* HidpNumberToString
********************************************************************************
*
*/
void HidpNumberToString(PWCHAR String, USHORT Number, USHORT stringLen)
{
const static WCHAR map[] = L"0123456789ABCDEF";
LONG i = 0;
ULONG nibble = 0;
ASSERT(stringLen);
for (i = stringLen-1; i >= 0; i--) {
String[i] = map[Number & 0x0F];
Number >>= 4;
}
}
/*
********************************************************************************
* CopyDeviceRelations
********************************************************************************
*
*
*/
PDEVICE_RELATIONS CopyDeviceRelations(PDEVICE_RELATIONS deviceRelations)
{
PDEVICE_RELATIONS newDeviceRelations;
if (deviceRelations){
ULONG size = sizeof(DEVICE_RELATIONS) + (deviceRelations->Count*sizeof(PDEVICE_OBJECT));
newDeviceRelations = MemDup(PagedPool, deviceRelations, size);
}
else {
newDeviceRelations = NULL;
}
return newDeviceRelations;
}
/*
********************************************************************************
* HidpQueryDeviceRelations
********************************************************************************
*
*
*/
NTSTATUS HidpQueryDeviceRelations(IN PHIDCLASS_DEVICE_EXTENSION hidClassExtension, IN OUT PIRP Irp)
{
PIO_STACK_LOCATION ioStack;
NTSTATUS ntStatus = STATUS_SUCCESS;
PAGED_CODE();
ASSERT(!hidClassExtension->isClientPdo);
ioStack = IoGetCurrentIrpStackLocation(Irp);
if (ioStack->Parameters.QueryDeviceRelations.Type == BusRelations) {
if (hidClassExtension->fdoExt.deviceRelations){
/*
* Don't call HidpCreateClientPDOs again if it's
* already been called for this device.
*/
ntStatus = STATUS_SUCCESS;
}
else {
ntStatus = HidpCreateClientPDOs(hidClassExtension);
}
if (NT_SUCCESS(ntStatus)){
ULONG i;
/*
* NTKERN expects a new pointer each time it calls QUERY_DEVICE_RELATIONS;
* it then FREES THE POINTER.
* So we have to return a new pointer each time, whether or not we actually
* created our copy of the device relations for this call.
*/
Irp->IoStatus.Information = (ULONG_PTR)CopyDeviceRelations(hidClassExtension->fdoExt.deviceRelations);
if (Irp->IoStatus.Information){
/*
* PnP dereferences each device object
* in the device relations list after each call.
* So for each call, add an extra reference.
*/
for (i = 0; i < hidClassExtension->fdoExt.deviceRelations->Count; i++){
ObReferenceObject(hidClassExtension->fdoExt.deviceRelations->Objects[i]);
hidClassExtension->fdoExt.deviceRelations->Objects[i]->Flags &= ~DO_DEVICE_INITIALIZING;
}
}
else {
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
}
}
DBGSUCCESS(ntStatus, TRUE)
}
else {
/*
* We don't support this option, so just maintain
* the current status (do not return STATUS_NOT_SUPPORTED).
*/
ntStatus = Irp->IoStatus.Status;
}
return ntStatus;
}
/*
********************************************************************************
* HidpQueryCollectionCapabilities
********************************************************************************
*
*
*/
NTSTATUS HidpQueryCollectionCapabilities( PDO_EXTENSION *pdoExt,
IN OUT PIRP Irp)
{
PDEVICE_CAPABILITIES deviceCapabilities;
PIO_STACK_LOCATION ioStack;
FDO_EXTENSION *fdoExt;
NTSTATUS status;
PAGED_CODE();
ASSERT(pdoExt->deviceFdoExt->Signature == HID_DEVICE_EXTENSION_SIG);
fdoExt = &pdoExt->deviceFdoExt->fdoExt;
ioStack = IoGetCurrentIrpStackLocation(Irp);
deviceCapabilities = ioStack->Parameters.DeviceCapabilities.Capabilities;
if (deviceCapabilities){
/*
* Set all fields for the collection-PDO as for the device-FDO
* by default.
*/
*deviceCapabilities = fdoExt->deviceCapabilities;
/*
* Now override the fields we care about.
*/
deviceCapabilities->LockSupported = FALSE;
deviceCapabilities->EjectSupported = FALSE;
deviceCapabilities->Removable = FALSE;
deviceCapabilities->DockDevice = FALSE;
deviceCapabilities->UniqueID = FALSE;
deviceCapabilities->SilentInstall = TRUE;
/*
* This field is very important;
* it causes HIDCLASS to get the START_DEVICE IRP immediately,
* if the device is not a keyboard or mouse.
*/
deviceCapabilities->RawDeviceOK = !pdoExt->MouseOrKeyboard;
/*
* This bit indicates that the device may be removed on NT
* without running the 'hot-unplug' utility.
*/
deviceCapabilities->SurpriseRemovalOK = TRUE;
DBGVERBOSE(("WAKE info: sysWake=%d devWake=%d; wake from D0=%d D1=%d D2=%d D3=%d.",
deviceCapabilities->SystemWake,
deviceCapabilities->DeviceWake,
(ULONG)deviceCapabilities->WakeFromD0,
(ULONG)deviceCapabilities->WakeFromD1,
(ULONG)deviceCapabilities->WakeFromD2,
(ULONG)deviceCapabilities->WakeFromD3))
status = STATUS_SUCCESS;
}
else {
status = STATUS_DEVICE_CONFIGURATION_ERROR;
}
Irp->IoStatus.Information = (ULONG_PTR)deviceCapabilities;
DBGSUCCESS(status, TRUE)
return status;
}
/*
********************************************************************************
* BuildCompatibleID
********************************************************************************
*
* Return a multi-string consisting of compatibility id's for this device
* in increasingly generic order (ending with HID_GENERIC_DEVICE).
*
* author: kenray
*
*/
PWSTR BuildCompatibleID(PHIDCLASS_DEVICE_EXTENSION hidClassExtension)
{
USHORT usage, usagePage;
ULONG spLength;
ULONG totLength;
ULONG i;
PWSTR specificId = NULL;
PWSTR compatIdList;
PWSTR genericId;
FDO_EXTENSION *fdoExt;
PAGED_CODE();
ASSERT(hidClassExtension->isClientPdo);
fdoExt = &hidClassExtension->pdoExt.deviceFdoExt->fdoExt;
ASSERT(ISPTR(fdoExt->deviceDesc.CollectionDesc));
i = hidClassExtension->pdoExt.collectionIndex;
usagePage = fdoExt->deviceDesc.CollectionDesc[i].UsagePage;
usage = fdoExt->deviceDesc.CollectionDesc[i].Usage;
switch (usagePage) {
case HID_USAGE_PAGE_GENERIC:
switch (usage) {
case HID_USAGE_GENERIC_POINTER:
case HID_USAGE_GENERIC_MOUSE:
specificId = HIDCLASS_SYSTEM_MOUSE;
break;
case HID_USAGE_GENERIC_KEYBOARD:
case HID_USAGE_GENERIC_KEYPAD:
specificId = HIDCLASS_SYSTEM_KEYBOARD;
break;
case HID_USAGE_GENERIC_JOYSTICK:
case HID_USAGE_GENERIC_GAMEPAD:
specificId = HIDCLASS_SYSTEM_GAMING_DEVICE;
break;
case HID_USAGE_GENERIC_SYSTEM_CTL:
specificId = HIDCLASS_SYSTEM_CONTROL;
break;
}
break;
case HID_USAGE_PAGE_CONSUMER:
specificId = HIDCLASS_SYSTEM_CONSUMER_DEVICE;
break;
default:
break;
}
spLength = (specificId) ? (WStrLen(specificId)+1) : 0;
totLength = spLength +
HIDCLASS_COMPATIBLE_ID_GENERIC_LENGTH +
HIDCLASS_COMPATIBLE_ID_STANDARD_LENGTH +
1;
compatIdList = ALLOCATEPOOL(NonPagedPool, totLength * sizeof(WCHAR));
if (compatIdList) {
RtlZeroMemory (compatIdList, totLength * sizeof(WCHAR));
if (specificId) {
RtlCopyMemory (compatIdList, specificId, spLength * sizeof (WCHAR));
}
genericId = compatIdList + spLength;
totLength = HIDCLASS_COMPATIBLE_ID_GENERIC_LENGTH;
RtlCopyMemory (genericId,
HIDCLASS_COMPATIBLE_ID_GENERIC_NAME,
totLength*sizeof(WCHAR));
HidpNumberToString (genericId + HIDCLASS_COMPATIBLE_ID_PAGE_OFFSET,
usagePage,
4);
HidpNumberToString (genericId + HIDCLASS_COMPATIBLE_ID_USAGE_OFFSET,
usage,
4);
RtlCopyMemory (genericId + totLength,
HIDCLASS_COMPATIBLE_ID_STANDARD_NAME,
HIDCLASS_COMPATIBLE_ID_STANDARD_LENGTH * sizeof (WCHAR));
}
return compatIdList;
}
/*
********************************************************************************
* SubstituteBusNames
********************************************************************************
*
* oldIDs is a multi-String of hardware IDs.
*
* 1. Return a new string with each "<busName>\" prefix replaced by "HID\".
*
* 2. If the device has multiple collections, append "&Colxx" to each id.
*
*/
PWCHAR SubstituteBusNames(PWCHAR oldIDs, FDO_EXTENSION *fdoExt, PDO_EXTENSION *pdoExt)
{
ULONG newIdLen;
PWCHAR id, newIDs;
ULONG numCollections;
WCHAR colNumStr[] = L"&Colxx";
PAGED_CODE();
numCollections = fdoExt->deviceDesc.CollectionDescLength;
ASSERT(numCollections > 0);
for (id = oldIDs, newIdLen = 0; *id; ){
ULONG thisIdLen = WStrLen(id);
/*
* This is a little sloppy because we're actually going to chop
* off the other bus name; but better this than walking each string.
*/
newIdLen += thisIdLen + 1 + sizeof("HID\\");
if (numCollections > 1){
newIdLen += sizeof(colNumStr)/sizeof(WCHAR);
}
id += thisIdLen + 1;
}
/*
* Add one for the extra NULL at the end of the multi-string.
*/
newIdLen++;
newIDs = ALLOCATEPOOL(NonPagedPool, newIdLen*sizeof(WCHAR));
if (newIDs){
ULONG oldIdOff, newIdOff;
/*
* Copy each string in the multi-string, replacing the bus name.
*/
for (oldIdOff = newIdOff = 0; oldIDs[oldIdOff]; ){
ULONG thisIdLen = WStrLen(oldIDs+oldIdOff);
ULONG devIdOff;
/*
* Copy the new bus name to the new string.
*/
newIdOff += WStrCpy(newIDs+newIdOff, L"HID\\");
/*
* Go past the old bus name in the old string.
*/
for (devIdOff = 0; oldIDs[oldIdOff+devIdOff]; devIdOff++){
if (oldIDs[oldIdOff+devIdOff] == L'\\'){
break;
}
}
/*
* Copy the rest of this device id.
*/
if (oldIDs[oldIdOff+devIdOff] == L'\\'){
devIdOff++;
}
else {
/*
* Strange -- no bus name in hardware id.
* Just copy the entire id.
*/
devIdOff = 0;
}
newIdOff += WStrCpy(newIDs+newIdOff, oldIDs+oldIdOff+devIdOff);
if (numCollections > 1){
/*
* If there is more than one collection,
* then also append the collection number.
*/
HidpNumberToString(colNumStr+4, (USHORT)pdoExt->collectionNum, 2);
newIdOff += WStrCpy(newIDs+newIdOff, colNumStr);
}
/*
* Go past the single string terminator.
*/
newIdOff++;
oldIdOff += thisIdLen + 1;
}
/*
* Add extra NULL to terminate multi-string.
*/
newIDs[newIdOff] = UNICODE_NULL;
}
return newIDs;
}
NTSTATUS
HidpQueryInterface(
IN PHIDCLASS_DEVICE_EXTENSION hidClassExtension,
IN OUT PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
PAGED_CODE();
ASSERT(hidClassExtension->isClientPdo);
irpSp = IoGetCurrentIrpStackLocation(Irp);
if (RtlEqualMemory(irpSp->Parameters.QueryInterface.InterfaceType,
&GUID_HID_INTERFACE_NOTIFY,
sizeof(GUID))) {
PDO_EXTENSION *pdoExt;
PHID_INTERFACE_NOTIFY_PNP notify;
notify = (PHID_INTERFACE_NOTIFY_PNP) irpSp->Parameters.QueryInterface.Interface;
if (notify->Size != sizeof(HID_INTERFACE_NOTIFY_PNP) ||
notify->Version < 1 ||
notify->StatusChangeFn == NULL) {
//
// return STATUS_UNSUPPORTED probably
//
return Irp->IoStatus.Status;
}
pdoExt = &hidClassExtension->pdoExt;
pdoExt->StatusChangeFn = notify->StatusChangeFn;
pdoExt->StatusChangeContext = notify->CallbackContext;
return STATUS_SUCCESS;
}
else if (RtlEqualMemory(irpSp->Parameters.QueryInterface.InterfaceType,
&GUID_HID_INTERFACE_HIDPARSE,
sizeof(GUID))) {
//
// Required for Generic Input, to remove the direct link
// b/w win32k and hidparse.
//
PHID_INTERFACE_HIDPARSE hidparse;
hidparse = (PHID_INTERFACE_HIDPARSE) irpSp->Parameters.QueryInterface.Interface;
if (hidparse->Size != sizeof(HID_INTERFACE_HIDPARSE) ||
hidparse->Version < 1) {
//
// return STATUS_UNSUPPORTED probably
//
return Irp->IoStatus.Status;
}
hidparse->HidpGetCaps = HidP_GetCaps;
return STATUS_SUCCESS;
}
//
// return STATUS_UNSUPPORTED probably
//
return Irp->IoStatus.Status;
}
/*
********************************************************************************
* HidpQueryIdForClientPdo
********************************************************************************
*
*
*
*/
NTSTATUS HidpQueryIdForClientPdo (
IN PHIDCLASS_DEVICE_EXTENSION hidClassExtension,
IN OUT PIRP Irp
)
{
PIO_STACK_LOCATION irpSp;
NTSTATUS status;
PDO_EXTENSION *pdoExt;
FDO_EXTENSION *fdoExt;
PAGED_CODE();
ASSERT(hidClassExtension->isClientPdo);
pdoExt = &hidClassExtension->pdoExt;
fdoExt = &pdoExt->deviceFdoExt->fdoExt;
irpSp = IoGetCurrentIrpStackLocation( Irp );
switch (irpSp->Parameters.QueryId.IdType) {
case BusQueryHardwareIDs:
/*
* Call down to get a multi-string of hardware ids for the PDO.
*/
IoCopyCurrentIrpStackLocationToNext(Irp);
status = HidpCallDriverSynchronous(fdoExt->fdo, Irp);
if (NT_SUCCESS(status)){
PWCHAR oldIDs, newIDs;
/*
* Replace the bus names in the current hardware IDs list with "HID\".
*/
oldIDs = (PWCHAR)Irp->IoStatus.Information;
Irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
newIDs = SubstituteBusNames(oldIDs, fdoExt, pdoExt);
ExFreePool(oldIDs);
if (newIDs){
/*
* Now append the compatible ids to the end of the HardwareIDs list.
*/
PWCHAR compatIDs = BuildCompatibleID(hidClassExtension);
if (compatIDs){
ULONG basicIDsLen, compatIDsLen;
PWCHAR allHwIDs;
/*
* Find the lengths of the id multi-strings (not counting the extra NULL at end).
*/
for (basicIDsLen = 0; newIDs[basicIDsLen]; basicIDsLen += WStrLen(newIDs+basicIDsLen)+1);
for (compatIDsLen = 0; compatIDs[compatIDsLen]; compatIDsLen += WStrLen(compatIDs+compatIDsLen)+1);
allHwIDs = ALLOCATEPOOL(PagedPool, (basicIDsLen+compatIDsLen+1)*sizeof(WCHAR));
if (allHwIDs){
RtlCopyMemory(allHwIDs, newIDs, basicIDsLen*sizeof(WCHAR));
RtlCopyMemory( allHwIDs+basicIDsLen,
compatIDs,
(compatIDsLen+1)*sizeof(WCHAR));
Irp->IoStatus.Information = (ULONG_PTR)allHwIDs;
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
ExFreePool(compatIDs);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
ExFreePool(newIDs);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
DBGSUCCESS(status, TRUE)
break;
case BusQueryDeviceID:
/*
* Call down to get a the device id for the device's PDO.
*/
IoCopyCurrentIrpStackLocationToNext(Irp);
status = HidpCallDriverSynchronous(fdoExt->fdo, Irp);
if (NT_SUCCESS(status)){
PWCHAR oldId, newId, tmpId;
/*
* Replace the bus name (e.g. "USB\") with "HID\" in the device name.
*/
/*
* First make this string into a multi-string.
*/
oldId = (PWCHAR)Irp->IoStatus.Information;
tmpId = ALLOCATEPOOL(PagedPool, (WStrLen(oldId)+2)*sizeof(WCHAR));
if (tmpId){
ULONG len = WStrCpy(tmpId, oldId);
/*
* Add the extra NULL to terminate the multi-string.
*/
tmpId[len+1] = UNICODE_NULL;
/*
* Change the bus name to "HID\"
*/
newId = SubstituteBusNames(tmpId, fdoExt, pdoExt);
if (newId){
Irp->IoStatus.Information = (ULONG_PTR)newId;
}
else {
status = STATUS_DEVICE_DATA_ERROR;
Irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
}
ExFreePool(tmpId);
}
else {
status = STATUS_DEVICE_DATA_ERROR;
Irp->IoStatus.Information = (ULONG_PTR)BAD_POINTER;
}
ExFreePool(oldId);
}
DBGSUCCESS(status, TRUE)
break;
case BusQueryInstanceID:
/*
* Produce an instance-id for this collection-PDO.
*
* Note: NTKERN frees the returned pointer, so we must provide a fresh pointer.
*/
{
PWSTR instanceId = MemDup(PagedPool, L"0000", sizeof(L"0000"));
if (instanceId){
ULONG i;
/*
* Find this collection-PDO in the device-relations array
* and make the id be the PDO's index within that array.
*/
for (i = 0; i < fdoExt->deviceRelations->Count; i++){
if (fdoExt->deviceRelations->Objects[i] == pdoExt->pdo){
swprintf(instanceId, L"%04x", i);
break;
}
}
ASSERT(i < fdoExt->deviceRelations->Count);
Irp->IoStatus.Information = (ULONG_PTR)instanceId;
status = STATUS_SUCCESS;
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
DBGSUCCESS(status, TRUE)
break;
case BusQueryCompatibleIDs:
// we now return the compatible id's at the end of HardwareIDs
// so that there is no UI on plug-in for a compatible-id match
// for a class-PDO.
// Irp->IoStatus.Information = (ULONG)BuildCompatibleID(hidClassExtension);
Irp->IoStatus.Information = (ULONG_PTR)ALLOCATEPOOL(PagedPool, sizeof(L"\0"));
if (Irp->IoStatus.Information) {
*(ULONG *)Irp->IoStatus.Information = 0; // double unicode-NULL.
status = STATUS_SUCCESS;
} else {
status = STATUS_INSUFFICIENT_RESOURCES;
ASSERT(0);
}
break;
default:
/*
* Do not return STATUS_NOT_SUPPORTED;
* keep the default status
* (this allows filter drivers to work).
*/
status = Irp->IoStatus.Status;
break;
}
return status;
}
/*
********************************************************************************
* AllClientPDOsInitialized
********************************************************************************
*
*
*/
BOOLEAN AllClientPDOsInitialized(FDO_EXTENSION *fdoExtension, BOOLEAN initialized)
{
BOOLEAN result = TRUE;
ULONG i;
if (ISPTR(fdoExtension->deviceRelations)){
for (i = 0; i < fdoExtension->deviceRelations->Count; i++){
PDEVICE_OBJECT pdo = fdoExtension->deviceRelations->Objects[i];
PHIDCLASS_DEVICE_EXTENSION pdoDevExt = pdo->DeviceExtension;
PDO_EXTENSION *pdoExt = &pdoDevExt->pdoExt;
/*
* Trick: compare !-results so that all TRUE values are equal
*/
if (!initialized == !(pdoExt->state == COLLECTION_STATE_UNINITIALIZED)){
DBGVERBOSE(("AllClientPDOsInitialized is returning FALSE for pdo %x, state = %d",
pdo, pdoExt->state))
result = FALSE;
break;
}
}
}
else {
result = !initialized;
}
return result;
}
/*
********************************************************************************
* AnyClientPDOsInitialized
********************************************************************************
*
*
*/
BOOLEAN AnyClientPDOsInitialized(FDO_EXTENSION *fdoExtension, BOOLEAN initialized)
{
BOOLEAN result = TRUE;
ULONG i;
if (ISPTR(fdoExtension->deviceRelations)){
for (i = 0; i < fdoExtension->deviceRelations->Count; i++){
PDEVICE_OBJECT pdo = fdoExtension->deviceRelations->Objects[i];
PHIDCLASS_DEVICE_EXTENSION pdoDevExt = pdo->DeviceExtension;
PDO_EXTENSION *pdoExt = &pdoDevExt->pdoExt;
if (!initialized != !(pdoExt->state == COLLECTION_STATE_UNINITIALIZED)){
result = TRUE;
break;
}
}
}
else {
result = !initialized;
}
return result;
}
/*
********************************************************************************
* HidpDeleteDeviceObjects
********************************************************************************
*
* Delete the device-FDO and collection-PDO's IF POSSIBLE.
* (must wait for REMOVE_DEVICE completion AND the IRP_MJ_CLOSE.
* Otherwise, return FALSE and we'll try again later.
*
*
*/
BOOLEAN HidpDeleteDeviceObjects(FDO_EXTENSION *fdoExt)
{
ULONG i;
/*
* Do this switch-a-roo to thwart re-entrancy problems.
*/
PDEVICE_OBJECT objToDelete = fdoExt->fdo;
fdoExt->fdo = BAD_POINTER;
if (ISPTR(fdoExt->deviceRelations)){
for (i = 0; i < fdoExt->deviceRelations->Count; i++){
PDO_EXTENSION *pdoExt = &fdoExt->collectionPdoExtensions[i]->pdoExt;
ASSERT(ISPTR(fdoExt->deviceRelations->Objects[i]));
if (ISPTR(pdoExt->name)){
RtlFreeUnicodeString(pdoExt->name);
ExFreePool(pdoExt->name);
pdoExt->name = BAD_POINTER;
}
/*
* Delete the client PDO.
* Don't touch the pdoExt after doing this.
*/
ObDereferenceObject(fdoExt->deviceRelations->Objects[i]);
IoDeleteDevice(fdoExt->deviceRelations->Objects[i]);
}
ExFreePool(fdoExt->deviceRelations);
}
fdoExt->deviceRelations = BAD_POINTER;
if (ISPTR(fdoExt->collectionPdoExtensions)){
ExFreePool(fdoExt->collectionPdoExtensions);
}
fdoExt->collectionPdoExtensions = BAD_POINTER;
ObDereferenceObject(objToDelete);
IoDeleteDevice(objToDelete);
return TRUE;
}
/*
********************************************************************************
* HidpQueryDeviceCapabilities
********************************************************************************
*
*
*
*/
NTSTATUS HidpQueryDeviceCapabilities( IN PDEVICE_OBJECT PdoDeviceObject,
IN PDEVICE_CAPABILITIES DeviceCapabilities)
{
PIRP irp;
NTSTATUS status;
PAGED_CODE();
irp = IoAllocateIrp(PdoDeviceObject->StackSize, FALSE);
if (irp) {
PIO_STACK_LOCATION nextStack;
KEVENT event;
nextStack = IoGetNextIrpStackLocation(irp);
ASSERT(nextStack);
nextStack->MajorFunction= IRP_MJ_PNP;
nextStack->MinorFunction= IRP_MN_QUERY_CAPABILITIES;
irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
KeInitializeEvent(&event, NotificationEvent, FALSE);
IoSetCompletionRoutine(irp,
HidpQueryCapsCompletion,
&event,
TRUE,
TRUE,
TRUE);
RtlZeroMemory(DeviceCapabilities, sizeof(DEVICE_CAPABILITIES));
/*
* Caller needs to initialize some fields
*/
DeviceCapabilities->Size = sizeof(DEVICE_CAPABILITIES);
DeviceCapabilities->Version = 1;
DeviceCapabilities->Address = -1;
DeviceCapabilities->UINumber = -1;
nextStack->Parameters.DeviceCapabilities.Capabilities = DeviceCapabilities;
status = IoCallDriver(PdoDeviceObject, irp);
if (status == STATUS_PENDING) {
KeWaitForSingleObject(
&event,
Suspended,
KernelMode,
FALSE,
NULL);
}
/*
* Note: we still own the IRP after the IoCallDriver() call
* because the completion routine returned
* STATUS_MORE_PROCESSING_REQUIRED.
*/
status = irp->IoStatus.Status;
IoFreeIrp(irp);
}
else {
status = STATUS_INSUFFICIENT_RESOURCES;
}
return status;
}
/*
********************************************************************************
* CheckReportPowerEvent
********************************************************************************
*
* Check whether the read report includes a power event.
* If it does, notify the system by completing the saved power-event Irp.
*
* Note: report should point to a "cooked" report with the report-id byte
* included at the beginning of the report, whether or not the device
* included the report id.
*
*/
VOID CheckReportPowerEvent( FDO_EXTENSION *fdoExt,
PHIDCLASS_COLLECTION collection,
PUCHAR report,
ULONG reportLen)
{
ULONG powerMask;
NTSTATUS status;
ASSERT(ISPTR(fdoExt->collectionPdoExtensions));
status = HidP_SysPowerEvent( report,
(USHORT)reportLen,
collection->phidDescriptor,
&powerMask);
if (NT_SUCCESS(status)){
if (powerMask){
/*
* This report contains a power event!
*/
PIRP irpToComplete = NULL;
KIRQL oldIrql;
KeAcquireSpinLock(&collection->powerEventSpinLock, &oldIrql);
/*
* We should have gotten a IOCTL_GET_SYS_BUTTON_EVENT earlier and queued
* an IRP to return now.
*/
if (ISPTR(collection->powerEventIrp)){
PDRIVER_CANCEL oldCancelRoutine;
/*
* "Dequeue" the power event IRP.
*/
irpToComplete = collection->powerEventIrp;
oldCancelRoutine = IoSetCancelRoutine(irpToComplete, NULL);
if (oldCancelRoutine){
ASSERT(oldCancelRoutine == PowerEventCancelRoutine);
}
else {
/*
* This IRP was cancelled and the cancel routine WAS called.
* The cancel routine will complete this IRP
* as soon as we drop the spinlock, so don't touch the IRP.
*/
ASSERT(irpToComplete->Cancel);
irpToComplete = NULL;
}
collection->powerEventIrp = BAD_POINTER;
}
else {
TRAP;
}
KeReleaseSpinLock(&collection->powerEventSpinLock, oldIrql);
/*
* If completing the IRP,
* do so after releasing all spinlocks.
*/
if (irpToComplete){
/*
* Complete the IRP with the power mask.
*
*/
ASSERT(irpToComplete->AssociatedIrp.SystemBuffer);
*(PULONG)irpToComplete->AssociatedIrp.SystemBuffer = powerMask;
irpToComplete->IoStatus.Information = sizeof(ULONG);
irpToComplete->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(irpToComplete, IO_NO_INCREMENT);
}
}
}
}
VOID ReadDeviceFlagsFromRegistry(FDO_EXTENSION *fdoExt, PDEVICE_OBJECT pdo)
{
NTSTATUS status;
HANDLE hRegDriver;
/*
* Open the driver registry key
* ( HKLM/System/CurrentControlSet/Control/Class/<GUID>/<#n> )
*/
status = IoOpenDeviceRegistryKey( pdo,
PLUGPLAY_REGKEY_DRIVER,
KEY_READ,
&hRegDriver);
if (NT_SUCCESS(status)){
UNICODE_STRING deviceSpecificFlagsName;
HANDLE hRegDeviceSpecificFlags;
/*
* See if the DeviceSpecificFlags subkey exists.
*/
RtlInitUnicodeString(&deviceSpecificFlagsName, L"DeviceSpecificFlags");
status = OpenSubkey( &hRegDeviceSpecificFlags,
hRegDriver,
&deviceSpecificFlagsName,
KEY_READ);
if (NT_SUCCESS(status)){
/*
* The registry DOES contain device-specific flags for this device.
*/
/*
* The key value information struct is variable-length.
* The actual length is equal to the length of the base
* PKEY_VALUE_FULL_INFORMATION struct + the length of
* the name of the key + the length of the value.
* (The name of the key is a 4-byte hex number representing
* the 16-bit "source" usage value, written as a wide-char
* string; so with the terminating '\0', 5 wide chars).
*/
#define MAX_DEVICE_SPECIFIC_FLAG_NAME_LEN 60
UCHAR keyValueBytes[sizeof(KEY_VALUE_FULL_INFORMATION)+(MAX_DEVICE_SPECIFIC_FLAG_NAME_LEN+1)*sizeof(WCHAR)+sizeof(ULONG)];
PKEY_VALUE_FULL_INFORMATION keyValueInfo = (PKEY_VALUE_FULL_INFORMATION)keyValueBytes;
ULONG actualLen;
ULONG keyIndex = 0;
do {
status = ZwEnumerateValueKey(
hRegDeviceSpecificFlags,
keyIndex,
KeyValueFullInformation,
keyValueInfo,
sizeof(keyValueBytes),
&actualLen);
if (NT_SUCCESS(status)){
PWCHAR valuePtr;
WCHAR valueBuf[2];
USHORT value;
ASSERT(keyValueInfo->Type == REG_SZ);
ASSERT(keyValueInfo->NameLength/sizeof(WCHAR) <= MAX_DEVICE_SPECIFIC_FLAG_NAME_LEN);
valuePtr = (PWCHAR)(((PCHAR)keyValueInfo)+keyValueInfo->DataOffset);
WStrNCpy(valueBuf, valuePtr, 1);
valueBuf[1] = L'\0';
value = (USHORT)LAtoX(valueBuf);
if (value){
if (!WStrNCmpI( keyValueInfo->Name,
L"AllowFeatureOnNonFeatureCollection",
keyValueInfo->NameLength/sizeof(WCHAR))){
DBGWARN(("Device HACK: allowing feature access on non-feature collections"))
fdoExt->deviceSpecificFlags |=
DEVICE_FLAG_ALLOW_FEATURE_ON_NON_FEATURE_COLLECTION;
}
}
keyIndex++;
}
} while (NT_SUCCESS(status));
ZwClose(hRegDeviceSpecificFlags);
}
ZwClose(hRegDriver);
}
else {
/*
* For 'raw' devices, IoOpenDeviceRegistryKey can fail on the
* initial 'raw' starts before the devnode is created.
*/
}
}
LONG WStrNCmpI(PWCHAR s1, PWCHAR s2, ULONG n)
{
ULONG result;
while (n && *s1 && *s2 && ((*s1|0x20) == (*s2|0x20))){
s1++, s2++;
n--;
}
if (n){
result = ((*s1|0x20) > (*s2|0x20)) ? 1 : ((*s1|0x20) < (*s2|0x20)) ? -1 : 0;
}
else {
result = 0;
}
return result;
}
ULONG LAtoX(PWCHAR wHexString)
/*++
Routine Description:
Convert a hex string (without the '0x' prefix) to a ULONG.
Arguments:
wHexString - null-terminated wide-char hex string
(with no "0x" prefix)
Return Value:
ULONG value
--*/
{
ULONG i, result = 0;
for (i = 0; wHexString[i]; i++){
if ((wHexString[i] >= L'0') && (wHexString[i] <= L'9')){
result *= 0x10;
result += (wHexString[i] - L'0');
}
else if ((wHexString[i] >= L'a') && (wHexString[i] <= L'f')){
result *= 0x10;
result += (wHexString[i] - L'a' + 0x0a);
}
else if ((wHexString[i] >= L'A') && (wHexString[i] <= L'F')){
result *= 0x10;
result += (wHexString[i] - L'A' + 0x0a);
}
else {
ASSERT(0);
break;
}
}
return result;
}
ULONG WStrNCpy(PWCHAR dest, PWCHAR src, ULONG n)
{
ULONG result = 0;
while (n && (*dest++ = *src++)){
result++;
n--;
}
return result;
}
NTSTATUS OpenSubkey( OUT PHANDLE Handle,
IN HANDLE BaseHandle,
IN PUNICODE_STRING KeyName,
IN ACCESS_MASK DesiredAccess
)
{
OBJECT_ATTRIBUTES objectAttributes;
NTSTATUS status;
PAGED_CODE();
InitializeObjectAttributes( &objectAttributes,
KeyName,
OBJ_CASE_INSENSITIVE,
BaseHandle,
(PSECURITY_DESCRIPTOR) NULL );
status = ZwOpenKey(Handle, DesiredAccess, &objectAttributes);
return status;
}
PVOID
HidpGetSystemAddressForMdlSafe(PMDL MdlAddress)
{
PVOID buf = NULL;
/*
* Can't call MmGetSystemAddressForMdlSafe in a WDM driver,
* so set the MDL_MAPPING_CAN_FAIL bit and check the result
* of the mapping.
*/
if (MdlAddress) {
MdlAddress->MdlFlags |= MDL_MAPPING_CAN_FAIL;
buf = MmGetSystemAddressForMdl(MdlAddress);
MdlAddress->MdlFlags &= (~MDL_MAPPING_CAN_FAIL);
}
else {
DBGASSERT(MdlAddress, ("MdlAddress passed into GetSystemAddress is NULL"), FALSE)
}
return buf;
}