/* ******************************************************************************** * * VXDCLNT.C * * * VXDCLNT - Sample Ring-0 HID device mapper for Memphis * * Copyright 1997 Microsoft Corp. * * (ep) * ******************************************************************************** */ #define INITGUID #include "vxdclnt.h" deviceContext *firstDevice = NULL, *lastDevice = NULL; VMM_SEMAPHORE shutdownSemaphore = (VMM_SEMAPHORE)NULL; BOOL ShutDown = FALSE; /* * Import function pointers */ t_pHidP_GetUsageValue pHidP_GetUsageValue = NULL; t_pHidP_GetScaledUsageValue pHidP_GetScaledUsageValue = NULL; t_pHidP_SetUsages pHidP_SetUsages = NULL; t_pHidP_GetUsages pHidP_GetUsages = NULL; t_pHidP_MaxUsageListLength pHidP_MaxUsageListLength = NULL; t_pIoGetDeviceClassAssociations pIoGetDeviceClassAssociations = NULL; t_pHidP_GetCaps pHidP_GetCaps = NULL; t_pHidP_GetValueCaps pHidP_GetValueCaps = NULL; #ifdef DEBUG UINT dbgOpt = 0; #endif /* * GetImportFunctionPtrs * * Set global pointers to imported functions from HIDPARSE and NTKERN. */ BOOL GetImportFunctionPtrs() { static BOOL haveAllPtrs = FALSE; if (!haveAllPtrs){ pHidP_GetUsageValue = (t_pHidP_GetUsageValue) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_GetUsageValue", NULL); pHidP_GetScaledUsageValue = (t_pHidP_GetScaledUsageValue) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_GetScaledUsageValue", NULL); pHidP_GetUsages = (t_pHidP_GetUsages) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_GetUsages", NULL); pHidP_SetUsages = (t_pHidP_SetUsages) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_SetUsages", NULL); pHidP_MaxUsageListLength = (t_pHidP_MaxUsageListLength) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_MaxUsageListLength", NULL); pIoGetDeviceClassAssociations = (t_pIoGetDeviceClassAssociations) _PELDR_GetProcAddress((struct HPEMODULE__ *)"ntpnp.sys", "IoGetDeviceClassAssociations", NULL); pHidP_GetCaps = (t_pHidP_GetCaps) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_GetCaps", NULL); pHidP_GetValueCaps = (t_pHidP_GetValueCaps) _PELDR_GetProcAddress((struct HPEMODULE__ *)"hidparse.sys", "HidP_GetValueCaps", NULL); if ( pHidP_GetUsageValue && pHidP_GetScaledUsageValue && pHidP_GetUsages && pHidP_SetUsages && pHidP_MaxUsageListLength && pIoGetDeviceClassAssociations && pHidP_GetCaps && pHidP_GetValueCaps){ haveAllPtrs = TRUE; } } return haveAllPtrs; } /* * WStrLen * */ ULONG WStrLen(PWCHAR str) { ULONG result = 0; while (*str++){ result++; } return result; } /* * NewDevice * * */ deviceContext *NewDevice( HANDLE devHandle, PHIDP_CAPS caps, PHIDP_PREPARSED_DATA desc, UINT descSize, PWCHAR deviceFileName) { deviceContext *newDevice; DBGOUT(("NewDevice()")); newDevice = (deviceContext *)_HeapAllocate(sizeof(deviceContext), 0); if (newDevice){ NTSTATUS ntstat; ULONG valueCapsLen; DBGOUT(("Allocated new device @ %xh ", (UINT)newDevice)); RtlZeroMemory(newDevice, sizeof(deviceContext)); newDevice->devHandle = devHandle; newDevice->readPending = FALSE; newDevice->next = NULL; RtlCopyMemory( (PVOID)&newDevice->deviceFileName, (PVOID)deviceFileName, (WStrLen(deviceFileName)*sizeof(WCHAR))+sizeof(UNICODE_NULL)); RtlCopyMemory((PVOID)&newDevice->hidCapabilities, (PVOID)caps, sizeof(HIDP_CAPS)); ExInitializeWorkItem(&newDevice->workItemRead, WorkItemCallback_Read, newDevice); ExInitializeWorkItem(&newDevice->workItemWrite, WorkItemCallback_Write, newDevice); /* * Allocate space for the device descriptor. */ newDevice->hidDescriptor = (PHIDP_PREPARSED_DATA)_HeapAllocate(descSize, 0); if (newDevice->hidDescriptor){ RtlCopyMemory((PVOID)newDevice->hidDescriptor, (PVOID)desc, descSize); } else { DBGERR(("_HeapAllocate for HID descriptor failed in NewDevice()")); goto _deviceInitError; } newDevice->writeReportQueueSemaphore = Create_Semaphore(0); if (!newDevice->writeReportQueueSemaphore){ goto _deviceInitError; } /* * Allocate space for the device report. */ newDevice->report = (PUCHAR)_HeapAllocate(newDevice->hidCapabilities.InputReportByteLength, 0); if (!newDevice->report){ DBGERR(("_HeapAllocate for report buffer failed in NewDevice()")); goto _deviceInitError; } /* * Figure out the length of the buttons value * and allocate a buffer for reading the buttons. */ newDevice->buttonListLength = pHidP_MaxUsageListLength(HidP_Input, HID_USAGE_PAGE_BUTTON, newDevice->hidDescriptor); DBGOUT(("Button values list length = %d.", newDevice->buttonListLength)); if (newDevice->buttonListLength){ newDevice->buttonValues = (PUSAGE) _HeapAllocate(newDevice->buttonListLength * sizeof (USAGE), 0); if (newDevice->buttonValues){ RtlZeroMemory(newDevice->buttonValues, newDevice->buttonListLength); } else { DBGERR(("HeapAlloc failed for button values buffer")); goto _deviceInitError; } } /* * Allocate the array of value-caps. */ valueCapsLen = caps->NumberInputValueCaps; if (valueCapsLen){ newDevice->valueCaps = (PHIDP_VALUE_CAPS)_HeapAllocate(valueCapsLen*sizeof(HIDP_VALUE_CAPS), 0); if (!newDevice->valueCaps){ DBGERR(("HeapAlloc failed for value caps")); goto _deviceInitError; } ntstat = pHidP_GetValueCaps(HidP_Input, newDevice->valueCaps, &valueCapsLen, desc); if (NT_SUCCESS(ntstat)){ /* * Read valueCaps structure for information about the types of * values returned by the device. */ } else { DBGERR(("HidP_GetValueCaps failed with %xh", ntstat)); goto _deviceInitError; } } else { DBGERR(("value caps length = 0!")); goto _deviceInitError; } } else { DBGERR(("_HeapAllocate failed in NewDevice()")); goto _deviceInitError; } return newDevice; _deviceInitError: if (newDevice){ if (newDevice->hidDescriptor){ _HeapFree(newDevice->hidDescriptor, 0); } if (newDevice->report){ _HeapFree(newDevice->report, 0); } if (newDevice->buttonValues){ _HeapFree(newDevice->buttonValues, 0); } if (newDevice->valueCaps){ _HeapFree(newDevice->valueCaps, 0); } _HeapFree(newDevice, 0); } return NULL; } /* * EnqueueDevice * */ VOID EnqueueDevice(deviceContext *device) { if (lastDevice){ lastDevice->next = device; lastDevice = device; } else { firstDevice = lastDevice = device; } device->next = NULL; } /* * DequeueDevice * */ VOID DequeueDevice(deviceContext *device) { deviceContext *prevDevice, *thisDevice; thisDevice = firstDevice; prevDevice = NULL; while (thisDevice){ if (thisDevice == device){ if (prevDevice){ prevDevice->next = thisDevice->next; if (!thisDevice->next){ lastDevice = prevDevice; } } else { if (thisDevice->next){ firstDevice = thisDevice->next; } else { firstDevice = lastDevice = NULL; } } thisDevice->next = NULL; break; } else { prevDevice = thisDevice; thisDevice = thisDevice->next; } } } /* * DestroyDevice * * Destroy the device context. * This function assumes the device context has already been dequeued * from the global list headed by firstDevice. * */ VOID DestroyDevice(deviceContext *device) { DBGOUT(("==> DestroyDevice()")); /* * Modify the device's internal workItem to do a close instead of a read. * Then queue the work item so that NtClose is called on a worker thread. */ ExInitializeWorkItem(&device->workItemClose, WorkItemCallback_Close, device); _NtKernQueueWorkItem(&device->workItemClose, DelayedWorkQueue); DBGOUT(("<== DestroyDevice()")); } /* * WorkItemCallback_Close * */ VOID WorkItemCallback_Close(PVOID context) { deviceContext *device = (deviceContext *)context; DBGOUT(("==> WorkItemCallback_Close()")); _NtKernClose(device->devHandle); if (device->hidDescriptor){ _HeapFree(device->hidDescriptor, 0); } if (device->report){ _HeapFree(device->report, 0); } if (device->buttonValues){ _HeapFree(device->buttonValues, 0); } if (device->valueCaps){ _HeapFree(device->valueCaps, 0); } if (device->writeReportQueueSemaphore){ Destroy_Semaphore(device->writeReportQueueSemaphore); } _HeapFree(device, 0); DBGOUT(("<== WorkItemCallback_Close()")); } /* * TryDestroyAll * * Destroy all devices which don't have a pending read. */ VOID TryDestroyAll() { deviceContext *thisDevice; DBGOUT(("=> TryDestroyAll()")); thisDevice = firstDevice; while (thisDevice){ deviceContext *nextDevice = thisDevice->next; // hold the next ptr in case we dequeue if (!thisDevice->readPending){ /* * No read pending on this device; we can shut it down. */ DequeueDevice(thisDevice); DestroyDevice(thisDevice); } thisDevice = nextDevice; } if (!firstDevice){ /* * All reads are complete and all devices have been destroyed. * If a shutdown is suspended, shutdown now. */ if (shutdownSemaphore){ Signal_Semaphore_No_Switch(shutdownSemaphore); } } DBGOUT(("<= TryDestroyAll()")); } /* * HandleShutdown * * */ VOID _cdecl HandleShutdown(VOID) { /* * Just set a flag. Wait for read completion to close the device handles. */ DBGOUT(("==> HandleShutdown")); TryDestroyAll(); if (firstDevice && !ShutDown){ /* * There are still reads pending. * Wait for all reads to complete before returning. */ ShutDown = TRUE; shutdownSemaphore = Create_Semaphore(0); if (shutdownSemaphore){ Wait_Semaphore(shutdownSemaphore, 0); Destroy_Semaphore(shutdownSemaphore); } } DBGOUT(("<== HandleShutdown")); } /* * HandleNewDevice * */ VOID _cdecl HandleNewDevice(VOID) { DBGOUT(("==> HandleNewDevice")); /* * See if there are any new device devices to connect. */ ConnectNTDeviceDrivers(); DBGOUT(("<== HandleNewDevice")); } /* * DeviceHasBeenOpened * * BUGBUG - there's got to be a better way of checking for this. * */ BOOL DeviceHasBeenOpened(PWCHAR deviceFileName, UINT nameWChars) { deviceContext *device; UINT nameLen = (nameWChars*sizeof(WCHAR))+sizeof(UNICODE_NULL); for (device = firstDevice; device; device = device->next){ if (memcmp(deviceFileName, device->deviceFileName, nameLen) == 0){ return TRUE; } } return FALSE; } /* * ConnectNTDeviceDrivers * * */ VOID ConnectNTDeviceDrivers() { WORK_QUEUE_ITEM *workItemOpen; workItemOpen = _HeapAllocate(sizeof(WORK_QUEUE_ITEM), 0); if (workItemOpen){ /* * Initialize the workItem and * pass the workItem itself as the context so that it can be freed later. */ ExInitializeWorkItem(workItemOpen, WorkItemCallback_Open, workItemOpen); DBGOUT(("==> ConnectNTDeviceDrivers() - queueing work item to call ")); /* * Queue a work item to do the open; this way we'll be on a worker thread * instead of (possibly) the NTKERN thread when we call NtCreateFile(). * This prevents a contention bug. */ _NtKernQueueWorkItem(workItemOpen, DelayedWorkQueue); } DBGOUT(("<== ConnectNTDeviceDrivers()")); } /* * WorkItemCallback_Open * * Do the actual work of opening the device. * */ VOID WorkItemCallback_Open(PVOID context) { IO_STATUS_BLOCK IoStatusBlock; NTSTATUS ntStatus; OBJECT_ATTRIBUTES Obja; UNICODE_STRING FileName; PWSTR symbolicLinkList = NULL; PWSTR symbolicLink; DBGOUT(("==> WorkItemCallback_Open()")); /* * The context is just the workItem itself, which can now be freed. */ ASSERT(context); _HeapFree(context, 0); /* * Get pointers to all our import functions at once. */ if (!GetImportFunctionPtrs()){ DBGERR(("ERROR: Failed to get import functions.")); return; } /* * Get a multi-string (separated by unicode NULL characters) * of symbolic link names to the input-class devices. */ ntStatus = pIoGetDeviceClassAssociations( (EXTERN_C GUID *)&GUID_CLASS_INPUT, NULL, 0, (PWSTR *)&symbolicLinkList); if (NT_ERROR(ntStatus) || !symbolicLinkList) { DBGERR(("pIoGetDeviceClassAssociations failed")); return; } /* * Go through all the device paths */ symbolicLink = symbolicLinkList; while ((WCHAR)*symbolicLink){ HANDLE deviceHandle; ULONG fileNameWChars; PWCHAR fileName; deviceContext *newDevice = NULL; /* * Get a pointer to to the next device name and step the multi-string pointer. */ fileName = symbolicLink; fileNameWChars = WStrLen(fileName); symbolicLink += fileNameWChars+1; /* * Make sure we don't already have this device open. * This can happen because we check for new device on every PNP_NEW_DEVNODE msg. */ if (DeviceHasBeenOpened(fileName, fileNameWChars)){ DBGOUT(("This device is already open, skipping ...")); } else { FileName.Buffer = fileName; FileName.Length = fileNameWChars*sizeof(WCHAR); FileName.MaximumLength = FileName.Length + sizeof(UNICODE_NULL); /* * Initialize an object-attribute structure with this filename. */ InitializeObjectAttributes(&Obja, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL); /* * Try to open the device. */ DBGOUT(("Opening HID Device : (unicode name @%xh, %xh wchars)", (UINT)fileName, (UINT)fileNameWChars)); ntStatus = _NtKernCreateFile( &deviceHandle, (GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES), &Obja, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, (FILE_SHARE_READ | FILE_SHARE_WRITE), FILE_OPEN, 0, NULL, 0 ); if (NT_SUCCESS(ntStatus)){ HID_COLLECTION_INFORMATION hidColInfo; DBGOUT(("Opened some device (handle=%xh), calling _NtKernDeviceIoControl", (UINT)deviceHandle)); ntStatus = _NtKernDeviceIoControl( deviceHandle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_HID_GET_COLLECTION_INFORMATION, NULL, 0, &hidColInfo, sizeof(HID_COLLECTION_INFORMATION)); if (NT_SUCCESS(ntStatus)){ PHIDP_PREPARSED_DATA phidDescriptor; phidDescriptor = (PHIDP_PREPARSED_DATA)_HeapAllocate(hidColInfo.DescriptorSize, 0); if (phidDescriptor){ ntStatus = _NtKernDeviceIoControl( deviceHandle, NULL, NULL, NULL, &IoStatusBlock, IOCTL_HID_GET_COLLECTION_DESCRIPTOR, NULL, 0, phidDescriptor, hidColInfo.DescriptorSize); if (NT_SUCCESS(ntStatus)){ HIDP_CAPS hidCaps; ntStatus = pHidP_GetCaps(phidDescriptor, (PHIDP_CAPS)&hidCaps); if (NT_SUCCESS(ntStatus)){ DBGOUT(("Opened HID device successfully, report size is %d.", (UINT)hidCaps.InputReportByteLength)); /* * <> * * Check hidCaps.UsagePage and hidCaps.Usage to verify that this is a device * that you want to drive. */ if (hidCaps.InputReportByteLength == 0){ DBGERR(("ERROR: Report size is zero!")); } else { /* * Take all the information we have for this device and bundle * it into a context. */ newDevice = NewDevice(deviceHandle, (PHIDP_CAPS)&hidCaps, phidDescriptor, hidColInfo.DescriptorSize, fileName); if (newDevice){ /* * Add this device to our global list. */ EnqueueDevice(newDevice); /* * Then start the first async read in the device device. */ DispatchNtReadFile(newDevice); } else { DBGERR(("NewDevice() failed")); } } } else { DBGERR(("pHidP_GetCaps failed")); } } else { DBGERR(("_NtKernDeviceIoControl (#2) failed")); } _HeapFree(phidDescriptor, 0); } else { DBGERR(("HeapAlloc failed")); } } else { DBGERR(("_NtKernDeviceIoControl failed")); } if (!newDevice){ DBGERR(("Device init failed -- calling _NtKernClose() on device handle")); _NtKernClose(deviceHandle); } } else { DBGERR(("_NtKernCreateFile failed to open this Device (ntStatus=%xh)", (UINT)ntStatus)); } } } // BUGBUG ExFreePool(symbolicLinkList); DBGOUT(("<== WorkItemCallback_Open()")); }