/*++ * * Component: hidserv.exe * File: pnp.c * Purpose: routines to support pnp hid devices. * * Copyright (C) Microsoft Corporation 1997,1998. All rights reserved. * * WGJ --*/ #include "hidserv.h" #include #include BOOL OpenHidDevice ( IN HDEVINFO HardwareDeviceInfo, IN PSP_DEVICE_INTERFACE_DATA DeviceInfoData, IN OUT PHID_DEVICE *HidDevice ); BOOL RebuildHidDeviceList ( void ) /*++ Routine Description: Do the required PnP things in order to find, the all the HID devices in the system at this time. --*/ { HDEVINFO hardwareDeviceInfo; SP_DEVICE_INTERFACE_DATA deviceInfoData; PHID_DEVICE hidDeviceInst; GUID hidGuid; DWORD i=0; PHID_DEVICE pCurrent, pTemp; HidD_GetHidGuid (&hidGuid); TRACE(("Getting class devices")); // // Open a handle to the plug and play dev node. // hardwareDeviceInfo = SetupDiGetClassDevs ( &hidGuid, NULL, // Define no enumerator (global) NULL, // Define no (DIGCF_PRESENT | // Only Devices present DIGCF_DEVICEINTERFACE)); // Function class devices. if (!hardwareDeviceInfo) { TRACE(("Get class devices failed")); return FALSE; } // // Take a wild guess to start // deviceInfoData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA); TRACE(("Marking existing devnodes")); // Unmark all existing nodes. They will be remarked if the device still exists. pCurrent = (PHID_DEVICE)HidDeviceList.pNext; while (pCurrent) { pCurrent->Active = FALSE; pCurrent = pCurrent->pNext; } TRACE(("Entering loop")); while (TRUE) { TRACE(("Enumerating device interfaces")); if (SetupDiEnumDeviceInterfaces (hardwareDeviceInfo, //HDEVINFO 0, // No care about specific PDOs //PSP_DEVINFO_DATA &hidGuid, // LPGUID i, //DWORD MemberIndex &deviceInfoData)) { //PSP_DEVICE_INTERFACE_DATA TRACE(("Got an item")); if (!OpenHidDevice (hardwareDeviceInfo, &deviceInfoData, &hidDeviceInst)) { TRACE(("Open hid device failed")); } else { if (StartHidDevice(hidDeviceInst)) { TRACE(("Start hid device succeeded.")); InsertTailList((PLIST_NODE)&HidDeviceList, (PLIST_NODE)hidDeviceInst); } else { WARN(("Failed to start hid device. (%x)", hidDeviceInst)); HidFreeDevice(hidDeviceInst); } } } else { DWORD error = GetLastError(); if (ERROR_NO_MORE_ITEMS == error) { TRACE(("No more items. Exitting")); break; } else { WARN(("Unexpected error getting device interface: 0x%xh", error)); } break; } i++; } TRACE(("Removing unmarked device nodes")); // RemoveUnmarkedNodes(); pCurrent = (PHID_DEVICE)HidDeviceList.pNext; while (pCurrent) { pTemp = pCurrent->pNext; if (!pCurrent->Active) { INFO(("Device (DevInst = %x) is gone.", pCurrent->DevInst)); RemoveEntryList((PLIST_NODE)&HidDeviceList, (PLIST_NODE)pCurrent); StopHidDevice(pCurrent); // this frees pCurrent } pCurrent = pTemp; } TRACE(("Destroying device info list")); SetupDiDestroyDeviceInfoList (hardwareDeviceInfo); return TRUE; } VOID HidFreeDevice(PHID_DEVICE HidDevice) { PHID_DATA data; UCHAR j; HidD_FreePreparsedData (HidDevice->Ppd); data = HidDevice->InputData; // // Release the button data // for (j = 0; j < HidDevice->Caps.NumberLinkCollectionNodes; j++, data++) { LocalFree(data->ButtonData.PrevUsages); LocalFree(data->ButtonData.Usages); } LocalFree(HidDevice->InputData); LocalFree(HidDevice->InputReportBuffer); LocalFree(HidDevice); } BOOL OpenHidDevice ( IN HDEVINFO HardwareDeviceInfo, IN PSP_DEVICE_INTERFACE_DATA DeviceInfoData, IN OUT PHID_DEVICE *HidDevice ) /*++ RoutineDescription: Given the HardwareDeviceInfo, representing a handle to the plug and play information, and deviceInfoData, representing a specific hid device, open that device and fill in all the relivant information in the given HID_DEVICE structure. return if the open and initialization was successfull or not. --*/ { PSP_DEVICE_INTERFACE_DETAIL_DATA functionClassDeviceData = NULL; SP_DEVINFO_DATA DevInfoData; ULONG predictedLength = 0; ULONG requiredLength = 0; UCHAR i = 0; PHID_DATA data = NULL; PHIDP_BUTTON_CAPS pButtonCaps = NULL; PHIDP_VALUE_CAPS pValueCaps = NULL; USHORT numCaps; PHIDP_LINK_COLLECTION_NODE LinkCollectionNodes = NULL; PHID_DEVICE hidDevice = NULL; WCHAR buf[512]; CONFIGRET cr = CR_SUCCESS; DEVINST devInst, parentDevInst; DWORD len = 0; if (!(hidDevice = LocalAlloc (LPTR, sizeof (HID_DEVICE)))) { // // Alloc failed. Drop out of the loop and let the device list // get deleted. // WARN(("Alloc HID_DEVICE struct failed.")); return FALSE; } TRACE(("Creating Device Node (%x)", hidDevice)); // // allocate a function class device data structure to receive the // goods about this particular device. // SetupDiGetDeviceInterfaceDetail ( HardwareDeviceInfo, DeviceInfoData, NULL, // probing so no output buffer yet 0, // probing so output buffer length of zero &requiredLength, NULL); // get the specific dev-node predictedLength = requiredLength; // sizeof (SP_FNCLASS_DEVICE_DATA) + 512; if (!(functionClassDeviceData = LocalAlloc (LPTR, predictedLength))) { WARN(("Allocation failed, our of resources!")); goto OpenHidDeviceError; } functionClassDeviceData->cbSize = sizeof (SP_DEVICE_INTERFACE_DETAIL_DATA); DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); DevInfoData.DevInst = 0; // // Retrieve the information from Plug and Play. // if (! SetupDiGetDeviceInterfaceDetail ( HardwareDeviceInfo, DeviceInfoData, // PSP_DEVICE_INTERFACE_DATA functionClassDeviceData, // PSP_DEVICE_INTERFACE_DETAIL_DATA predictedLength, &requiredLength, &DevInfoData)) { //PSP_DEVINFO_DATA WARN(("SetupDiGetDeviceInterfaceDetail failed")); goto OpenHidDeviceError; } INFO(("Just got interface detail for %S", functionClassDeviceData->DevicePath)); hidDevice->DevInst = DevInfoData.DevInst; // // // // Find out it this is a set of speakers with buttons on it. This is for // but 136800. We want to only emit WM_APPCOMMANDs for speakers, not the // VK. This is because certain games leave the opening movie scene when // you press any key, so if someone presses volume down on their speakers // it will leave the scene. They just want that to affect volume. // cr = CM_Get_Parent(&parentDevInst, DevInfoData.DevInst, 0); // // We need to get the grandparent, then get the child, to make sure that // we get the first child in the set. From there, if the child we've got // is the same parent of the devnode that we started with, we want to // look at its sibling. But if the devnode we've got is different from // the parent, then we've got the right one to look at! // if (cr == CR_SUCCESS) { cr = CM_Get_Parent(&devInst, parentDevInst, 0); } if (cr == CR_SUCCESS) { cr = CM_Get_Child(&devInst, devInst, 0); } if (cr == CR_SUCCESS) { if (devInst == parentDevInst) { // // Only look at the first sibling, because this covers all sets // of speakers currently on the market. // cr = CM_Get_Sibling(&devInst, devInst, 0); } if (cr == CR_SUCCESS) { len = sizeof(buf); cr = CM_Get_DevNode_Registry_Property(devInst, CM_DRP_CLASS, NULL, buf, &len, 0); if (cr == CR_SUCCESS) { if (lstrcmpi(TEXT("MEDIA"), buf) == 0) { hidDevice->Speakers = TRUE; } } } // else - definitely not speakers } // // // // Do we already have this device open? { PHID_DEVICE pCurrent = (PHID_DEVICE)HidDeviceList.pNext; while (pCurrent) { if (pCurrent->DevInst == hidDevice->DevInst) break; pCurrent = pCurrent->pNext; } if (pCurrent) { // Yes. Mark it and bail on the new node. pCurrent->Active = TRUE; INFO(("Device (DevInst = %x) already open.", DevInfoData.DevInst)); goto OpenHidDeviceError; } else { // No. Mark the new node and continue. INFO(("Device (DevInst = %x) is new.", DevInfoData.DevInst)); hidDevice->Active = TRUE; } } hidDevice->HidDevice = CreateFile ( functionClassDeviceData->DevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, // no SECURITY_ATTRIBUTES structure OPEN_EXISTING, // No special create flags FILE_FLAG_OVERLAPPED, // Do overlapped read/write NULL); // No template file if (INVALID_HANDLE_VALUE == hidDevice->HidDevice) { INFO(("CreateFile failed - %x (%S)", GetLastError(), functionClassDeviceData->DevicePath)); goto OpenHidDeviceError; } else { INFO(("CreateFile succeeded Handle(%x) - %S", hidDevice->HidDevice, functionClassDeviceData->DevicePath)); } if (!HidD_GetPreparsedData (hidDevice->HidDevice, &hidDevice->Ppd)) { WARN(("HidD_GetPreparsedData failed")); goto OpenHidDeviceError; } if (!HidD_GetAttributes (hidDevice->HidDevice, &hidDevice->Attributes)) { WARN(("HidD_GetAttributes failed")); goto OpenHidDeviceError; } if (!HidP_GetCaps (hidDevice->Ppd, &hidDevice->Caps)) { WARN(("HidP_GetCaps failed")); goto OpenHidDeviceError; } // ***Instructive comment from KenRay: // At this point the client has a choice. It may chose to look at the // Usage and Page of the top level collection found in the HIDP_CAPS // structure. In this way it could just use the usages it knows about. // If either HidP_GetUsages or HidP_GetUsageValue return an error then // that particular usage does not exist in the report. // This is most likely the preferred method as the application can only // use usages of which it already knows. // In this case the app need not even call GetButtonCaps or GetValueCaps. // If this is a collection we care about, continue. Else, we get out now. if (hidDevice->Caps.UsagePage != HIDSERV_USAGE_PAGE) { TRACE(("This device is not for us (%x)", hidDevice)); goto OpenHidDeviceError; } // // setup Input Data buffers. // TRACE(("NumberLinkCollectionNodes = %d", hidDevice->Caps.NumberLinkCollectionNodes)); { ULONG numNodes = hidDevice->Caps.NumberLinkCollectionNodes; if (!(LinkCollectionNodes = LocalAlloc(LPTR, hidDevice->Caps.NumberLinkCollectionNodes*sizeof(HIDP_LINK_COLLECTION_NODE)))) { WARN(("LinkCollectionNodes alloc failed.")); goto OpenHidDeviceError; } HidP_GetLinkCollectionNodes(LinkCollectionNodes, &numNodes, hidDevice->Ppd); for (i=0; iCaps.NumberLinkCollectionNodes; i++) { INFO(("Link Collection [%d] Type = %x, Alias = %x", i, LinkCollectionNodes[i].CollectionType, LinkCollectionNodes[i].IsAlias)); INFO(("Link Collection [%d] Page = %x, Usage = %x", i, LinkCollectionNodes[i].LinkUsagePage, LinkCollectionNodes[i].LinkUsage)); } } // // Allocate memory to hold on input report // if (!(hidDevice->InputReportBuffer = (PCHAR) LocalAlloc (LPTR, hidDevice->Caps.InputReportByteLength * sizeof (CHAR)))) { WARN(("InputReportBuffer alloc failed.")); goto OpenHidDeviceError; } // // Allocate memory to hold the button and value capabilities. // NumberXXCaps is in terms of array elements. // if (!(pButtonCaps = (PHIDP_BUTTON_CAPS) LocalAlloc (LPTR, hidDevice->Caps.NumberInputButtonCaps*sizeof (HIDP_BUTTON_CAPS)))) { WARN(("buttoncaps alloc failed.")); goto OpenHidDeviceError; } if (!(pValueCaps = (PHIDP_VALUE_CAPS) LocalAlloc (LPTR, hidDevice->Caps.NumberInputValueCaps*sizeof (HIDP_VALUE_CAPS)))) { WARN(("valuecaps alloc failed.")); goto OpenHidDeviceError; } // // Have the HidP_X functions fill in the capability structure arrays. // numCaps = hidDevice->Caps.NumberInputButtonCaps; TRACE(("NumberInputButtonCaps = %d", numCaps)); HidP_GetButtonCaps (HidP_Input, pButtonCaps, &numCaps, hidDevice->Ppd); numCaps = hidDevice->Caps.NumberInputValueCaps; TRACE(("NumberInputValueCaps = %d", numCaps)); HidP_GetValueCaps (HidP_Input, pValueCaps, &numCaps, hidDevice->Ppd); TRACE(("Buttons:")); for (i=0; iCaps.NumberInputButtonCaps; i++) { TRACE(("UsagePage = 0x%x", pButtonCaps[i].UsagePage)); TRACE(("LinkUsage = 0x%x", pButtonCaps[i].LinkUsage)); TRACE(("LinkUsagePage = 0x%x\n", pButtonCaps[i].LinkUsagePage)); } // // Allocate a buffer to hold the struct _HID_DATA structures. // hidDevice->InputDataLength = hidDevice->Caps.NumberLinkCollectionNodes + hidDevice->Caps.NumberInputValueCaps; if (!(hidDevice->InputData = data = (PHID_DATA) LocalAlloc (LPTR, hidDevice->InputDataLength * sizeof (HID_DATA)))) { WARN(("InputData alloc failed.")); goto OpenHidDeviceError; } TRACE(("InputDataLength = %d", hidDevice->InputDataLength)); // // Fill in the button data // Group button sets by link collection. // for (i = 0; i < hidDevice->Caps.NumberLinkCollectionNodes; i++, data++) { data->IsButtonData = TRUE; data->LinkUsage = LinkCollectionNodes[i].LinkUsage; data->UsagePage = LinkCollectionNodes[i].LinkUsagePage; if (i) data->LinkCollection = i; else data->LinkCollection = HIDP_LINK_COLLECTION_ROOT; INFO(("Button Link Usage = %x", data->LinkUsage)); INFO(("Button Link Usage Page = %x", data->UsagePage)); INFO(("Button Link Collection = %x", data->LinkCollection)); data->Status = HIDP_STATUS_SUCCESS; data->ButtonData.MaxUsageLength = HidP_MaxUsageListLength ( HidP_Input, hidDevice->Caps.UsagePage, hidDevice->Ppd); //make room for the terminator data->ButtonData.MaxUsageLength++; if (!(data->ButtonData.Usages = (PUSAGE_AND_PAGE) LocalAlloc (LPTR, data->ButtonData.MaxUsageLength * sizeof (USAGE_AND_PAGE)))) { WARN(("Usages alloc failed.")); goto OpenHidDeviceError; } if (!(data->ButtonData.PrevUsages = (PUSAGE_AND_PAGE) LocalAlloc (LPTR, data->ButtonData.MaxUsageLength * sizeof (USAGE_AND_PAGE)))) { WARN(("PrevUsages alloc failed.")); goto OpenHidDeviceError; } } // // Fill in the value data // for (i = 0; i < hidDevice->Caps.NumberInputValueCaps; i++, data++) { if (pValueCaps[i].IsRange) { WARN(("Can't handle value ranges!!")); } data->IsButtonData = FALSE; data->LinkUsage = pValueCaps[i].LinkUsage; data->UsagePage = pValueCaps[i].LinkUsagePage; if (pValueCaps[i].LinkCollection) data->LinkCollection = pValueCaps[i].LinkCollection; else data->LinkCollection = HIDP_LINK_COLLECTION_ROOT; INFO(("Value Link Usage = %x", data->LinkUsage)); INFO(("Value Link Usage Page = %x", data->UsagePage)); INFO(("Value Link Collection = %x", data->LinkCollection)); INFO(("Value LogicalMin = %x", pValueCaps[i].LogicalMin)); INFO(("Value LogicalMax = %x", pValueCaps[i].LogicalMax)); data->ValueData.LogicalRange = pValueCaps[i].LogicalMax - pValueCaps[i].LogicalMin; data->Status = HIDP_STATUS_SUCCESS; data->ValueData.Usage = pValueCaps[i].NotRange.Usage; } LocalFree(pButtonCaps); LocalFree(pValueCaps); LocalFree(LinkCollectionNodes); LocalFree(functionClassDeviceData); *HidDevice = hidDevice; return TRUE; OpenHidDeviceError: if (data) { for (i = 0; i < hidDevice->Caps.NumberLinkCollectionNodes; i++, data++) { if (data->ButtonData.Usages) { LocalFree(data->ButtonData.Usages); } if (data->ButtonData.PrevUsages) { LocalFree(data->ButtonData.PrevUsages); } } LocalFree(data); } if (pValueCaps) { LocalFree(pValueCaps); } if (pButtonCaps) { LocalFree (pButtonCaps); } if (hidDevice->InputReportBuffer) { LocalFree (hidDevice->InputReportBuffer); } if (LinkCollectionNodes) { LocalFree (LinkCollectionNodes); } if (hidDevice->Ppd) { HidD_FreePreparsedData (hidDevice->Ppd); } if (hidDevice->HidDevice && hidDevice->HidDevice != INVALID_HANDLE_VALUE) { CloseHandle (hidDevice->HidDevice); } if (functionClassDeviceData) { LocalFree (functionClassDeviceData); } LocalFree (hidDevice); return FALSE; } BOOL StartHidDevice( PHID_DEVICE pHidDevice ) /*++ RoutineDescription: Create a work thread to go with the new hid device. This thread lives as long as the associated hid device is open. --*/ { // // Init read sync objects // pHidDevice->ReadEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (!pHidDevice->ReadEvent) { WARN(("Failed creating read event.")); return FALSE; } pHidDevice->CompletionEvent = CreateEvent(NULL, TRUE, TRUE, NULL); if (!pHidDevice->CompletionEvent) { CloseHandle(pHidDevice->ReadEvent); WARN(("Failed creating read event.")); return FALSE; } // event handle for overlap. pHidDevice->Overlap.hEvent = pHidDevice->CompletionEvent; // // Create hid work thread // pHidDevice->fThreadEnabled = TRUE; pHidDevice->ThreadHandle = CreateThread( NULL, // pointer to thread security attributes 0, // initial thread stack size, in bytes (0 = default) HidThreadProc, // pointer to thread function pHidDevice, // argument for new thread 0, // creation flags &pHidDevice->ThreadId // pointer to returned thread identifier ); if (!pHidDevice->ThreadHandle) { CloseHandle(pHidDevice->ReadEvent); CloseHandle(pHidDevice->CompletionEvent); WARN(("Failed creating hid work thread.")); return FALSE; } // Register device nofication for this file handle // This only required for NT5 { DEV_BROADCAST_HANDLE DevHdr; ZeroMemory(&DevHdr, sizeof(DevHdr)); DevHdr.dbch_size = sizeof(DEV_BROADCAST_HANDLE); DevHdr.dbch_devicetype = DBT_DEVTYP_HANDLE; DevHdr.dbch_handle = pHidDevice->HidDevice; pHidDevice->hNotify = RegisterDeviceNotification( hWndHidServ, &DevHdr, DEVICE_NOTIFY_WINDOW_HANDLE); if (!pHidDevice->hNotify) { WARN(("RegisterDeviceNotification failure (%x).", GetLastError())); } } // // Start the read // SetEvent(pHidDevice->ReadEvent); return TRUE; } BOOL StopHidDevice( PHID_DEVICE pHidDevice ) /*++ RoutineDescription: Eaxh device has a thread that needs to be cleaned up when the device is "stopped". Here we signal the thread to exit and clean up. --*/ { HANDLE hThreadHandle; DWORD dwResult; TRACE(("StopHidDevice (%x)", pHidDevice)); // without a device, nothing can be done. if (!pHidDevice) return FALSE; // Doing this here prevents us from seeing // DBT_DEVICEQUERYREMOVEFAILED since the notify handle // is gone. However, this is acceptable since there is // nothing useful we will do in response to that event // anyway. UnregisterDeviceNotification(pHidDevice->hNotify); hThreadHandle = pHidDevice->ThreadHandle; // // Allow the hid work thread to exit. // pHidDevice->fThreadEnabled = FALSE; // Signal the read event, in case thread is waiting there INFO(("Set Read Event.")); SetEvent(pHidDevice->ReadEvent); INFO(("Waiting for work thread to exit...")); WaitForSingleObject(hThreadHandle, INFINITE); TRACE(("StopHidDevice (%x) done.", pHidDevice)); return TRUE; } BOOL DestroyHidDeviceList( void ) /*++ RoutineDescription: Unlike a rebuild, all devices here are closed so the process can exit. --*/ { PHID_DEVICE pNext, pCurrent = (PHID_DEVICE)HidDeviceList.pNext; while (pCurrent) { RemoveEntryList((PLIST_NODE)&HidDeviceList, (PLIST_NODE)pCurrent); pNext = pCurrent->pNext; StopHidDevice(pCurrent); pCurrent = pNext; } return TRUE; } BOOL DestroyDeviceByHandle( HANDLE hDevice ) /*++ RoutineDescription: Here we need to remove a specific device. --*/ { PHID_DEVICE pCurrent = (PHID_DEVICE)HidDeviceList.pNext; while (pCurrent) { if (hDevice == pCurrent->HidDevice) { RemoveEntryList((PLIST_NODE)&HidDeviceList, (PLIST_NODE)pCurrent); #if WIN95_BUILD // // Can't do the UnregisterDeviceNotification in the same context // as when we receive the WM_DEVICECHANGE DBT_REMOVEDEVICECOMPLETE // for a DBT_DEVTYP_HANDLE // PostMessage(hWndHidServ, WM_HIDSERV_STOP_DEVICE, 0, (LPARAM)pCurrent); #else StopHidDevice(pCurrent); #endif // WIN95_BUILD break; } pCurrent = pCurrent->pNext; } return TRUE; }