windows-nt/Source/XPSP1/NT/drivers/wdm/input/samples/vxdclnt/vxdclnt.c
2020-09-26 16:20:57 +08:00

714 lines
23 KiB
C

/*
********************************************************************************
*
* 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));
/*
* <<COMPLETE>>
*
* 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()"));
}