Êíèãà: Writing Windows WDM Device Drivers

User Mode HID Clients

User Mode HID Clients

The DDK recommends that you talk to HID devices using a user mode application, if possible. This makes sense, as it is usually far easier to write and debug a Win32 application than a device driver. See the next section if you really need to write a HID client device driver. A user mode HID application has three main jobs to do.

1. Find all HID devices.

2. For each HID device, inspect its capabilities to see if it is of interest.

3. Read HID input reports or write HID output reports when needed. Alternatively send and receive feature reports.

The HidUsbUser example in HidUsbUserHidKbdUser.cpp illustrates these tasks. HidUsbUser looks for a HID keyboard, reads input keys until Esc is pressed, and then — hey hey — flashes the keyboard LEDs. HidUsbUser does not care what sort of HID keyboard is attached. All HID keyboards ought to respond in a similar way.

HidUsbUser uses various standard routines. The HidD… routines are available only to user mode applications. The "parsing routines" HidP… can be used by kernel mode clients as well. In user mode, all these routines are provided by Hid.dll. In kernel mode, the parsing routines are in HidParse.sys.

Finding HID Devices

The HID class driver registers all its device objects as having the HID device interface. HidUsbUser can, therefore, use the GetDeviceViaInterface routine to find all existing HID devices. Instead of using one of the book software GUIDs, HidUsbUser looks for the HID class GUID. Use HidD_GetHidGuid to find this GUID.

GUID HidGuid;
HidD_GetHidGuid(&HidGuid);

Going back to Chapter 5, you will see that GetDeviceViaInterface uses the SetupDi… routines to find a device that matches the given GUID. Previously, I have always looked for the first device with the desired device interface. HidUsbUser, however, looks through all the available HID devices looking for a HID keyboard.

GetDeviceViaInterface opens a handle to each device. HidUsbUser then gets each device's capabilities. If it is a HID keyboard, the main program carries on. Otherwise it closes the device handle, and loops again so that GetDeviceViaInterface can open the next HID device. HidUsbUser reports an error and gives up if a HID keyboard is not found.

Note that HidUsbUser only looks for an appropriate HID device when it starts. As it stands, it will not discover any new HID devices that suddenly are plugged in. The Win32 RegisterDeviceNotification function can be used to listen for Plug and Play Notification events in user mode applications. The Wdm2Notify application described in Chapter 9 shows how to use RegisterDeviceNotification to listen for device interface events. This can easily be modified to spot new HID class devices.

If the HID USB keyboard is unceremoniously unplugged while HidUsbUser is running, all I/O requests fail, and GetLastError returns 1167 (ERROR_PROCESS_ABORTED).

Getting HID Capabilities

HidUsbUser uses GetCapabilities to get a HID device's capabilities, as shown in Listing 23.1. In particular, it looks for a HID keyboard. If it finds a keyboard, GetCapabilities returns a preparsed data pointer and the maximum input and output report sizes. The preparsed data is the HID Report descriptor parsed into a format that makes it easier for the support routines to decode and encode reports. You will need to pass the preparsed data pointer whenever you call any of the HID parsing routines.

Some applications will simply want to look for the device vendor and product IDs. If you know the capabilities of your device, there is no need to do any more checking. However, you still need to get the preparsed data. If possible, you should write code that does not rely on specific vendor and product IDs.

Get the device ID using HidD_GetAttributes. This fills in the supplied HIDD_ATTRIBUTES structure. If successful, the VendorID, ProductID, and VersionNumber fields are filled in. For USB devices, these values come from the USB Device descriptor.

Use HidD_GetPreparsedData to obtain a pointer to the preparsed data. This time, Windows allocates the buffer and returns a pointer to you. Make sure that you keep the preparsed data pointer handy for the rest of your HID operations. When finished with it, free the preparsed data memory using HidD_FreePreparsedData.

The first step when analyzing your device capabilities is to call HidP_GetCaps, which fills in a HIDP_CAPS structure. The UsagePage and Usage fields in there tell you the usage information for the Report descriptor top-level collection. In GetCapabilities, this is all the information that is needed to determine if the device is a keyboard. It checks that these fields are HID_ USAGE_PAGE_GENERIC and HID_USAGE_GENERIC_KEYBOARD, respectively.

The HIDP_CAPS structure also contains much other useful information. First, it contains the maximum length of input, output, and feature reports in three fields. The first two of these values are saved, because they indicate how big a buffer to allocate to send and receive reports. The other HIDP_CAPS fields are used if you check the detailed control capabilities.

Listing 23.1 Getting a HID device's capabilities

bool GetCapabilities(HANDLE hHidKbd, PHIDP_PREPARSED_DATA& HidPreparsedData, USHORT& InputReportLen, USHORT& OutputReportLen) {
 // Get attributes, i.e. find vendor and product ids
 HIDD_ATTRIBUTES HidAttributes;
 if (!HidD_GetAttributes(hHidKbd, &HidAttributes)) {
  printf("XXX Could not get HID attributesn");
  return false;
 }
 printf("HID attributes: VendorID=%04X, ProductID=%04X, VersionNumber=%04Xn",
  HidAttributes.VendorID, HidAttributes.ProductID, HidAttributes.VersionNumber);
 // Get preparsed data
 if (!HidD_GetPreparsedData( hHidKbd, &HidPreparsedData)) {
  printf("XXX Could not get HID preparsed datan");
  return false;
 }
 // Work out capabilities
 HIDP_CAPS HidCaps;
 bool found = false;
 NTSTATUS status = HidP_GetCaps(HidPreparsedData, &HidCaps);
 if (status==HIDP_STATUS_SUCCESS) {
  printf("Top level Usage page %d usage %dn", HidCaps.UsagePage, HidCaps.Usage);
  if (HidCaps.UsagePage==HID_USAGE_PAGE_GENERIC && HidCaps.Usage==HD_USAGE_GENERIC_KEYBOARD) {
   printf(" Found HID keyboardnn");
   found = true;
  }
  // Remember max lengths of input and output reports
  InputReportLen = HidCaps.InputReportByteLength;
  OutputReportLen = HidCaps.OutputReportByteLength;
  printf("InputReportByteLength %dn", HidCaps.InputReportByteLength);
  printf("OutputReportByteLength %dn", HidCaps.OutputReportByteLength);
  printf("FeatureReportByteLength %dnn", HidCaps.FeatureReportByteLength);
  printf("NumberLinkCollectionNodes %dnn", HidCaps.NumberLinkCollectionNodes);
  printf("NumberInputButtonCaps %dn", HidCaps.NumberInputButtonCaps);
  printf("NumberInputValueCaps %dn", HidCaps.NumberInputValueCaps);
  printf("NumberOutputButtonCaps %dn", HidCaps.NumberOutputButtonCaps);
  printf("NumberOutputValueCaps %dn", HidCaps.NumberOutputValueCaps);
  printf("NumberFeatureButtonCaps %dn", HidCaps.NumberFeatureButtonCaps);
  printf("NumberFeatureValueCaps %dnn", HidCaps.NumberFeatureValueCaps);
  ShowButtonCaps("Input button capabilities", HidP_Input, HidCaps.NumberInputButtonCaps, HidPreparsedData);
  ShowButtonCaps("Output button capabilities", HidP_Output, HidCaps.NumberOutputButtonCaps, HidPreparsedData);
 }
 return found;
}

Why Get Button and Value Capabilities?

Some programs cannot just rely on the HIDP_CAPS UsagePage and Usage fields. For example, in the future, some fancy device may use these fields to say that it is a "speech interface". A "speech interface" might still be able to generate key presses (e.g., when a user says a word). Its detailed capabilities would show that it could indeed present data that is of interest.

Each control in a HID device is seen by Windows as being either a button or a value. Anything that has a HID usage is defined as being a button. Controls that take on any other values are called values.

Each of the keys on a keyboard has a HID usage. All keyboard keys are in usage page 7. Within that usage page, most of the usages from 0 to 255 have key definitions. However, most Western keyboards produce usage codes in two ranges. Usages 0 to 101 contain most standard keys, while usages 224 to 231 represent the modifier keys such as Shift, Ctrl, Alt, etc. To be extra careful, check that the HID device generates usages in these two ranges.

Similarly to double check that the HID device has the correct LEDs check that its output report contains buttons in usage page 8, for LEDs. Within this usage page, usage 1 corresponds to NumLock, usage 2 with CapsLock, and usage 3 with ScrollLock.

Getting Button Capabilities

The HIDP_CAPS structure has fields that tell you how many button and value capabilities there are for each type of report. For example, NumberInputButtonCaps tells you how many button capabilities there are for input reports.

GetCapabilities uses the ShowButtonCaps routine shown in Listing 23.2 to show what button capabilities there are for different types of report. The NumCaps parameter is passed the number of button capabilities that are expected for the specified type of report.

ShowButtonCaps first allocates an array of HIDP_BUTTON_CAPS structures called ButtonCaps. The call to HidP_GetButtonCaps fills in this array. Afterwards, ShowButtonCaps simply goes through each element in ButtonCaps and prints out the usages that can be returned. The HIDP_BUTTON_CAPS UsagePage field gives the usage page of all the buttons referred to in this structure. ReportId specifies the report in which these buttons are. If the IsRange BOOLEAN is TRUE, the Range.UsageMin and Range.UsageMax fields are valid. Otherwise, the Not Range. Usage field holds the only valid usage.

Each HIDP_BUTTON_CAPS structure has similar fields for the string descriptors and physical designators associated with controls. Finally, various link fields specify in which collection the buttons are. See the previous chapter for a description of collections.

Listing 23.2 Getting buttons capabilities

void ShowButtonCaps(char* Msg, HIDP_REPORT_TYPE ReportType, USHORT NumCaps, PHIDP_PREPARSED_DATA HidPreparsedData) {
 if (NumCaps==0) return;
 printf(" %sn", Msg);
 HIDP_BUTTON_CAPS* ButtonCaps = new HIDP_BUTTON_CAPS[NumCaps];
 if( ButtonCaps==NULL) return;
 NTSTATUS status = HidP_GetButtonCaps(ReportType, ButtonCaps, &NumCaps, HidPreparsedData);
 if (status==HIDP_STATUS_SUCCESS) {
  for (USHORT i=0; i<NumCaps; i++) {
   printf("ButtonCaps[%d].UsagePage %dn", i, ButtonCaps[i].UsagePage);
   if (ButtonCaps[i].IsRange) printf(".Usages %d..%dnn", ButtonCaps[i].Range.UsageMin, ButtonCaps[i].Range.UsageMax);
   else printf(".Usage %dnn", ButtonCaps[i].NotRange.Usage);
  }
 }
 delete ButtonCaps;
}

Here is an excerpt from the output produced by the ShowButtonCaps routine. It confirms that, in this case, the HID device has the desired controls. It could be fairly laborious if you had to check that all the right buttons were available, as the button capabilities could arrive in any order.

Input button capabilities
ButtonCaps[0].UsagePage 7
             .Usages 224..231
ButtonCaps[1].UsagePage 7
             .Usages 0..101
Output button capabilities
ButtonCaps[0].UsagePage 8
             .Usages 1..3

Use HidP_GetSpecificButtonCaps if you need to look for buttons in a different collection or search for a controls with a specific usage page or usage. This might be a better way of determining whether the controls you are interested in are supported.

Getting Value Capabilities

You can retrieve details of what control values are supported in a very similar way. Use HidP_GetValueCaps to get a list of all values in the top-level collection. Alternatively, HidP_GetSpecificValueCaps is used to look for values in a different collection, or search for controls with a specific usage page or usage. The information is stored in an array of HIDP_VALUE_CAPS structures.

Getting Collection Capabilities

The HidP_GetLinkCollectionNodes function is used to obtain details of all the collections in a top-level collection. It fills in an array of HIDP_LINK_COLLECTION_NODE structures. See the DDK for full details of how this array specifies the arrangement of collections within the top-level collection.

Reading Input Reports

Reading input reports from a HID device is straightforward. Listing 23.3 shows how this is done in the HidUsbUser main routine. It keeps reading input reports until the Esc key is pressed on the HID keyboard. See Listing 23.6 for some example output from this routine.

First, allocate and zero a buffer to receive a report, with the size given in the HIDP_CAPS structure. A keyboard input report is always eight bytes long. However, the InputReportByteLength field in HIDP_CAPS is one longer than this, as the first byte is used to indicate the report ID. For keyboard reports, this first byte will be zero, as report IDs are not used.

Use ReadFile to read the next available input report. If the device can return two different input reports then this call may obtain either of them. Suppose these two reports have different lengths. For the smaller input report, the returned count of bytes transferred will be less than the buffer size you passed.

Listing 23.3 Reading keyboard input reports

DWORD TxdBytes;
char* InputReport = new char[InputReportLen];
assert(InputReport!=NULL);
// Loop until Esc pressed on keyboard
do {
 if (!ReadFile( hHidKbd, InputReport, InputReportLen, &TxdBytes, NULL)) {
  printf("XXX Could not read value %dn", GetLastError());
  break;
 } else if (TxdBytes==InputReportLen) {
  printf(" Input report %d:", InputReport[0]);
  for(USHORT i=1; i<InputReportLen; i++) printf(" %02X", InputReport[i]);
  printf("n");
  DecodeInputUsages(InputReport, InputReportLen, HidPreparsedData);
 } else {
  printf("XXX Wrong number of bytes read: %dn", TxdBytes);
  break;
 }
} while (InputReport[3]!=0x29);
delete InputReport;

What Buttons Were Set in My Report?

You could just look directly at the received buffer and work out what it means. However, the correct HID way to analyze reports is get the HID parsing routines to tell you what usages were set in the report. DecodeInputUsages, shown in Listing 23.4, does just this job. Indeed, it goes further by telling you what changes have occurred since the last input report. It prints out the usages that have just been "made" and the ones have just been "broken".

The HidP_GetButtonsEx function analyses an input report buffer and reports which button usages were set in the report. The output is an array of USAGE_AND_PAGE structures (i.e., usage page and usage values).

DecodeInputUsages must first find out the maximum size for this output array using HidP_MaxUsageListLength, so that a suitably sized array can be allocated. Actually, DecodeInputUsages cheats, as I know the maximum size in advance. I use the MaxPreviousUsages constant to declare fixed-size arrays; this avoids allocating and freeing buffers all over the place.

The maximum usage list length for a keyboard report is 14. This is made up of the six keys that the HID Report descriptor says can be pressed simultaneously, and the eight modifier keys, again that the Report descriptor says can be pressed simultaneously. Obviously, it is extremely unlikely that 14 keys can be pressed at the same time.

HidP_GetButtonsEx analyses the input report just received and fills in the array of USAGE_AND_PAGE structures, called Usages. The ValidUsages variable is filled with the number of valid elements in this array. DecodeInputUsages simply prints out the usage page and usage for each of these valid key presses.

The next job, if desired, is to work out what has changed since the last input report. The HidP_UsageListDifference function does this. It is passed the previous usage list and the current usage list as input. It fills in two further arrays, one with a list of usages that have been "made" (or just arrived), and one with a list of usages that have just "broken" (or gone away). DecodeInputUsages prints out these two arrays. Note that HidP_UsageListDifference deals with arrays of USAGE values, not USAGE_AND_PAGEs. In DecodeInputUsages all the input usages are in the keyboard usage page, so this is not a problem.

Listing 23.4 Decoding input report usages

const ULONG MaxPreviousUsages = 14;
USAGE_AND_PAGE Usages[MaxPreviousUsages];
USAGE PreviousUsages[MaxPreviousUsages];
void DecodeInputUsages(char* KbdReport, USHORT KbdReportLen, PHIDP_PREPARSED_DATA HidPreparsedData) {
 // Get max number of USAGE_ANQ_PAGEs required for all input reports in
 // top-level collection
 ULONG MaxUsages = HidP_MaxUsageListLength(HidP_Input, 0, HidPreparsedData);
 if (MaxUsages==0 || MaxUsages>MaxPreviousUsages) {
  printf("XXX Invalid HidP_MaxUsageListLength returned %dn", MaxUsages);
  return;
 }
 // Get usages set in given keyboard report
 ULONG ValidUsages = MaxUsages;
 NTSTATUS status = HidP_GetButtonsEx(HidP_Input, 0, Usages, &ValidUsages, HidPreparsedData, KbdReport, KbdReportLen);
 if (status==HIDP_STATUS_SUCCESS) {
  USAGE CurrentUsages[MaxPreviousUsages];
  USAGE BreakUsages[MaxPreviousUsages];
  USAGE MakeUsages[MaxPreviousUsages];
  // Show current usages
  memset(CurrentUsages, 0, sizeof(CurrentUsages));
  printf(" Usages set: ");
  for (ULONG i=0; i<ValidUsages; i++) {
   printf( " %02X:%02X", Usages[i].UsagePage, Usages[i].Usage);
   CurrentUsages[i] = Usages[i].Usage;
  }
  // Work out differences compared to previous usages
  HidP_UsageListDifference(PreviousUsages, CurrentUsages, BreakUsages, MakeUsages, MaxUsages);
  // Print out usages broken and made
  printf(" (Break: ");
  for (i=0; i<MaxUsages; i++) {
   if (BreakUsages[i]==0) break;
   printf(" %02X", BreakUsages[i]);
  }
  printf(") (Make: ");
  for(i=0; i<MaxUsages; i++) {
   if (MakeUsages[i]==0) break;
   printf(" %02X", MakeUsages[i]);
  }
  printf(")nn");
  // Save previous usages
  memcpy(PreviousUsages, CurrentUsages, MaxUsages*sizeof(USAGE));
 }
}

The HidP_GetButtons function can be used if you are only looking for buttons in a particular usage page. Both HidP_GetButtons and HidP_GetButtonsEx have parameters that let you look for buttons in a particular collection.

What Values Were Set in My Report?

Use the HidP_GetUsageValue, HidP_GetScaledUsageValue, or HidP_GetUsageValueArray functions to retrieve control values.

Îãëàâëåíèå êíèãè


Ãåíåðàöèÿ: 1.235. Çàïðîñîâ Ê ÁÄ/Cache: 3 / 0
ïîäåëèòüñÿ
Ââåðõ Âíèç