52964.fb2 Writing Windows WDM Device Drivers - читать онлайн бесплатно полную версию книги . Страница 22

Writing Windows WDM Device Drivers - читать онлайн бесплатно полную версию книги . Страница 22

Chapter 21USB Driver Interface

This chapter describes how to write a USB driver that uses the Windows USB Driver Interface (USBDI). The UsbKbd example driver talks to a USB keyboard and retrieves the raw keyboard input data. The corresponding Win32 test application displays this input data. In addition, you finally get the opportunity to flash some LEDs (the LEDs on keyboard).

A USB client is a device driver that uses the standard Windows system USB class drivers to talk to USB devices using USBDI. USBD.sys is the USB class driver. It uses either UHCD.sys to talk to Universal Host Controller Interface devices, or OpenHCI.sys for Open Host Controller Interface devices. USBHUB.sys is the USB driver for root hubs and external hubs. The relevant drivers are loaded when the PCI Enumerator finds each USB host controller.

Windows 2000 runs USBDI version 2.00, while Windows 98 uses version 1.01. There is no documentation on the differences. USBDI version 2.0 might return some Windows Management Instrumentation (WMI) data. Use the USBD_GetUSBDIVersion function to determine what version of the USB class drivers you are using. Only one very minor aspect of this chapter's driver is affected by the USBDI version.

As far as a USB client driver is concerned, the host computer (the Windows PC) talks directly to any function device that is plugged in. The details of bus enumeration and how information is transferred are not directly of concern to a USB client.

However, a client does need to be aware of the types of transfer, when they occur, and their timing, particularly in the case of isochronous transfers.

A client talks to a function device through one or more pipes. A pipe is a unidirectional or bidirectional channel for data transfer between the host and a device. A pipe may be one of four types. Control pipes transfer commands to the device (including USB set up and configuration information), with data transfer in either direction. Interrupt pipes transfer device-specific information to the host (e.g., when a keyboard key is pressed). Bulk pipes transfer larger amounts of data. Isochronous pipes carry time-sensitive data, such as voice.

Control endpoints are bidirectional. Interrupt endpoints are unidirectional into the host. Bulk and isochronous endpoints are unidirectional, in either direction.

The default pipe is used for control transfers. The USB specification defines several standard requests on the default pipe, such as "get descriptor". In addition, a device may respond to class-defined or vendor-defined requests on the default pipe.

Figure 21.1 shows the logical entities that a device and USBDI present to the client. A device exposes a series of connection points for pipes called endpoints. Endpoints may be grouped together to make an Interface. A configuration consists of one or more interfaces. A device usually contains just one configuration and one interface, but can contain more.

Figure 21.1 USB logical structure

Only one configuration is active at any one time, but all the interfaces and their endpoints within that configuration are active. Windows forces the selection of a configuration. As explained in the previous chapter, Windows forms a Hardware ID out of the USB Device descriptor, Vendor ID, and Product ID fields. This Hardware ID is used to try to find a matching device driver installation INF file. If there is no match, a Compatible ID is formed from the class fields in each interface descriptor in the selected configuration. These Compatible IDs are used to search for class-specific installation INF files; a device is created for each interface in the device.

USB Client Driver Design

A USB client driver is a standard WDM driver with Plug and Play support. As described in Chapters 11 and 20, the USB hub driver detects when a new USB device is inserted. The PnP Manager uses the vendor ID or device class information to select the driver to run. The driver's AddDevice routine is called and other PnP IRPs are issued as normal. Client USB drivers never receive any hardware resources, such ports or interrupts, as the USB class drivers handle all the low-level I/O.

USB devices are particularly prone to surprise removals, when the user accidentally (or deliberately) pulls out the USB plug. In Windows 2000, you will receive a Surprise Removal PnP IRP, while in Windows 98, only a Remove Device PnP IRP. These requests must succeed even if there are open handles to the device. Any transfers that are in progress must be aborted.

By definition, a USB client driver makes calls, at its lower edge, to the USB class drivers. However, it can implement whatever upper edge it deems necessary. The UsbKbd example driver in this chapter exposes a device interface that allows Win32 applications to read raw keyboard data and issue various IOCTLs. However, your driver could take a totally different approach, depending on what your USB device does.

The UsbKbd example driver is based on the Wdm2 driver, with Power Management support removed. The UsbKbd driver source code is in the book software UsbKbd\Sys directory. I suggest that you use UsbKbd as the basis for your USB client driver. Most of the USB handling routines are in the file Usb.cpp. You will need to alter the upper edge interface that is implemented in the dispatch routines in Dispatch.cpp.

A real USB client driver should support Power Management. An article on the Microsoft website at www.microsoft.com/hwdev entitled "OnNow requirements in the USB Core Specification" discusses this issue.

Using UsbKbd

UsbKbd devices can be found by Win32 applications using the USBKBD_GUID device interface. When a handle to a UsbKbd device is opened, the UsbKbd driver starts talking to the USB peripheral. If the peripheral is a HID USB keyboard, its configuration is selected, thereby enabling the keyboard interrupt pipe. The UsbKbd Create IRP handler also obtains some more information from the device and the USB class drivers, displaying DebugPrint trace output in the checked build of the driver.

Closing the file handle deselects the keyboard's configuration.

The Win32 application can then use standard ReadFile calls to obtain raw keyboard data from UsbKbd. The raw data is in the form a HID keyboard input report, sent on a USB interrupt pipe. Chapter 22 details the format of this report precisely. All you need to know for now is that the first byte has all the modifier key bits (such as Shift, Control, etc.). The third and following bytes contain the HID keypress codes for all the keys that are currently pressed.

The time-out defaults to 10 seconds for ReadFile calls. One of the UsbKbd IOCTLs can be used to alter the time-out.

The UsbKbd driver also accepts WriteFile calls to change the state of the keyboard LEDs. The first byte of the WriteFile data is sent as a HID output report in a control transfer along the default pipe to Endpoint 0. The lower five bits of this byte are defined, though most keyboards just have three LEDs.

Table 21.1 shows the IOCTLs that UsbKbd supports. These functions are provided just to let you see how to exercise the USB interface. In most cases, you would not want to expose such IOCTLs for general use, although they might be useful for debugging purposes.

Table 21.1 UsbKbd IOCTLs

IOCTL_USBKBD_SET_READ_TIMEOUTInputULONG Time-out in secondsSpecifies the time-out for subsequent read requests
IOCTL_USBKBD_GET_DEVICE_DESCRIPTOROutputReceived device descriptorRead the device descriptor.
IOCTL_USBKBD_GET_CONFIGURATION_DESCRIPTORSOutputReceived descriptorsRead the first configuration descriptor, along with any associated, interface, endpoint, class and vendor descriptors.
IOCTL_USBKBD_GET_SPECIFIED_DESCRIPTORInputULONG Descriptor type, ULONG Descriptor sizeRead the specified descriptor into the given sized buffer
OutputReceived descriptor
IOCTL_USBKBD_GET_STATUSESOutput6-byte buffer for status valuesRead the device, first interface and first endpoint status values (16 bits each)
IOCTL_USBKBD_GET_FRAME_INFOOutput12-byte buffer for frame informationRead the ULONG current frame length (in bits), the ULONG current frame number and the ULONG frame number for the next frame whose length can be altered

UsbKbd Installation

For your USB client driver, you will use a standard installation INF file. In the Version section, make sure that you include the following lines.

Class=USB

ClassGUID={36FC9E60-C465-11CF-8056-444553540000}

As described in Chapter 11, your driver will be loaded if it has the right Hardware ID. A Hardware ID is constructed using the vendor and product IDs in the device descriptor (e.g., USB\VID_046A&PID_0001).

However, for USB keyboards, this approach does not work. Windows 98 and Windows 2000 have built in support for USB keyboards. In both cases, they load the Human Input Device (HID) class drivers. The standard keyboard driver then gets key input through HID client drivers.

I tried fiddling with the USB keyboard installation INF files for Windows 98 and Windows 2000. However, I could not persuade Windows to load UsbKbd in place of the standard drivers. I can only assume that the standard driver names are hard-coded into the kernel and that the Windows INF files are not really used.

My brutal solution to this problem was to replace the HID USB minidriver HidUsb.sys with the UsbKbd.sys driver executable. Windows uses HidUsb.sys to process HID USB devices. Replacing HidUsb.sys seems to work. Needless to say, this approach stops you from using the USB keyboard for normal typing. In addition, it stops you from using any other USB HID devices. Therefore, you will need to have a standard PCBIOS keyboard connected to the system, as well. Do not forget to save a copy of the HidUsb.sys file (or delete it and reinstall it from the Windows CD). You need a dual-boot PC if you are going to change HidUsb.sys in W2000.

If you do not have a HID USB keyboard at hand, you obviously cannot run these tests. However, I show some of the DebugPrint output later in this chapter, so you can see what is going on.

While developing the UsbKbd driver, I altered makefile.inc so that it copied the final executable, UsbKbd.sys, to overwrite HidUsb.sys. However, this command line has now been commented out, so you will have to do the copy yourself.

Trying out each new version of the UsbKbd driver was very easy. Simply unplugging and plugging in the USB cable caused the old driver to be unloaded and new one loaded. Windows 2000 displays a bugcheck on shutdown when HidUsb.sys is replaced by UsbKbd.sys. The simple solution is to unplug the USB keyboard before you shutdown.

Headers and Libraries

The USB header files that you might need to include are in the DDK.

usb100.hVarious USB constants and structures
usbioctl.hIOCTL definitions
usbdlib.hURB building and assorted routines
usbdi.hUSBDI routines, including URB structures

These header files generate two annoying compiler warnings which you can ignore. I also found that I needed to specifically mention the USB library in the SOURCES file.

TARGETLIBS=C:\NTDDK\LIB\I386\FREE\Usbd.Lib

USBDI IOCTLs

The USB class drivers are primarily used through the USB Device Interface (USBDI) Internal IOCTLs shown in Table 21.2. As these are Internal IOCTLs, they are only available to other parts of the kernel, such as device drivers, and are not available to user mode applications.

A few ordinary IOCTLs are also available to Win32 programs. These are intended for use by diagnostic utilities, so I shall not cover them here. The DDK UsbView utility source shows how to use these IOCTLs.

Table 21.2 USBDI internal IOCTLs

IOCTL_INTERNAL_USB_SUBMIT_URBSubmit URBBlock awaiting result
IOCTL_INTERNAL_USB_RESET_PORTReset and reenable a port
IOCTL_INTERNAL_USB_GET_PORT_STATUSGet port status bits: USBD_PORT_ENABLED USBD_PORT_CONNECTED
IOCTL_INTERNAL_USB_ENABLE_PORTReenable a disabled port
IOCTL_INTERNAL_USB_GET_HUB_COUNTUsed internally by hub driver
IOCTL_INTERNAL_USB_CYCLE_PORTSimulates a device unplug and replug
IOCTL_INTERNAL_USB_GET_ROOTHUB_PDOUsed internally by hub driver
IOCTL_INTERNAL_USB_GET_HUB_NAMEGet the device name of the USB hub
IOCTL_INTERNAL_USB_GET_BUS_INFOFills in a USB_BUS_NOTIFICATION structure (W2000 only)
IOCTL_INTERNAL_USB_GET_CONTROLLER_NAMEGet the host controller device name (W2000 only)

URBs

The most important Internal IOCTL is IOCTL_INTERNAL_USB_SUBMIT_URB, which lets you submit a USB Request Block (URB) for processing by the USB class drivers. There are thirty-odd different URB function codes. USB clients use URBs to do most of their hard work.

The URB structure itself is a union of some 16 different _URB_* structures, as shown in Listing 21.1. Each function code uses one of these other URB structures to detail its input or output parameters. All URB structures begin with a common header _URB_HEADER structure. The header Length and Function fields must be filled in before calling the USB Device Interface. The result of processing the URB is returned in the Status field.

Listing 21.1 URB structures

typedef struct _URB {

 union {

  struct _URB_HEADER UrbHeader;

  struct _URB_SELECT_INTERFACE UrbSelectInterface;

  struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;

  // …

 };

} URB, *PURB;

struct _URB_HEADER {

 USHORT Length;

 USHORT Function;

 USBD_STATUS Status;

 // …

};

The Status field uses its top two bits as a State code to indicate how the request completed. Table 21.3 shows the possible values along with the names of macros that can be used to detect these conditions. The rest of the Status field is filled with more detailed error codes. Some error codes are internal system USB errors (e.g., no memory).

Table 21.3 URB Status field State code bits

State code bitsInterpretationMacro
00Completed successfullyUSBD_SUCCESS
01Request is pendingUSBD_PENDING
10Error, endpoint not stalledUSBD_ERROR
11Error, endpoint stalledUSBD_ERROR or USBD_HALTED

To make it easier to construct suitable URBs, various build macros are provided, such as UsbBuildGetDescriptorRequest, which fill in a preallocated URB. Other useful routines both allocate the memory for a URB and fill it in.

The reference section towards the end of this chapter lists the URB function codes. The reference section also details the other crucial USB structures. However, this chapter first illustrates how to perform most common USB actions by describing how these jobs are done in the UsbKbd driver.

Several URB structures have a UrbLink field. If non-NULL, this specifies a pointer to a URB that is processed if the current one completes successfully.

Calling USBDI

Listing 21.2 shows the Call USBDI routine in Usb.cpp. This is used to issue all the Internal lOCTLs to the USB system class drivers. It has default parameters that make it easy to ask for a URB to be processed.

CallUSBDI has to create a new IRP for the Internal IOCTL, fill in the IRP, and send off the IRP down the device stack to the USB system drivers. Further, it then waits until the IRP has been processed. CallUSBDI can only be called at PASSIVE_LEVEL

The USB Internal IOCTLs do not use the standard input and output IRP stack locations. Instead, the stack Parameters.Others.Argument1 field is set to the URB pointer, etc. The Parameters.Others.Argument2 field is used for one of the USB IOCTLs.

Allocating IRPs

If you want to call a lower driver, it is usually simplest to reuse an existing IRP. You simply fill in the next IRP stack location with the correct function codes and parameters and call the next driver down the stack.

In some cases however, you will need to build an IRP from scratch. For example, you may wish to generate an IRP in your DriverEntry routine. Alternatively, you may process a large incoming request by splitting it into several different IRPs.

UsbKbd could reuse an existing IRP. However, it is straightforward to allocate a new IOCTL IRP. The CallUSBDI routine keeps the entire IRP allocation process wrapped up in one neat location using this technique.

Building and issuing an IOCTL IRP is made particularly easy with the IoBuildDeviceIoControlRequest call. If you pass an initialized event, you can wait for the IRP to complete simply by waiting for the event to become signalled. You do not need to set up a completion routine.

IoBuilDeviceIoControlRequest can be used to make both IOCTL and Internal IOCTL IRPs, depending on the InternalDeviceIoControl parameter. In a similar way to the Win32 DeviceIoControl call, both input and output buffers can be used. The IOCTL control code must use buffered I/O.

Internally, it seems as though IoBuildDeviceIoControlRequest allocates some nonpaged memory to hold the combined input and output buffer, to make it work exactly like a standard IOCTL call. IoBuildDeviceIoControlRequest copies the input buffer there initially. It must set its own completion routine that copies the data back into your given output buffer (as well as setting the event and freeing the IRP memory).

The USB Internal IOCTLs do not use the standard input and output buffers[51]. Instead, you have to set up the next stack location Parameters.Others.Argument1 field. This is more complicated than you might think.

Previous examples have used IoSkipCurrentIrpStackLocation to reuse the current IRP stack location, and IoCopyCurrentIrpStackLocationToNext to copy the current stack location to the next when setting a completion routine. We cannot use these routines, as the current stack location is not set up yet.

The IoCallDriver call moves onto the next IRP stack location. CallUSBDI wants to change the stack location that the next lower driver sees. The IoGetNextIrpStackLocation call returns the required IRP stack location pointer[52]. The IoBuildDeviceIoControlRequest call has already set up most of the correct values for this stack location. The required values are set into the Parameters.Others.Argument1 field, etc.

In summary, CallUSBDI does the following jobs.

1. Initializes an IRP completion event

2. Builds an Internal IOCTL

3. Stores the URB pointer, etc., in the IRP's next stack location

4. Calls the next driver

5. If the request is still pending, wait for the completion event to become signalled. The KeWaitForSingleObjectWaitReason parameter must be set to Suspended.

Listing 21.2 Calling USBDI

NTSTATUS CallUSBDI(IN PUSBKBD_DEVICE_EXTENSION dx, IN PVOID UrbEtc,

 IN ULONG IoControlCode/*=IOCTL_INTERNAL_USB_SUBMIT_URB*/,

 IN ULONG Arg2/*=0*/) {

 IO_STATUS_BLOCK IoStatus;

 KEVENT event;

 // Initialise IRP completion event

 KeInitializeEvent(&event, NotificationEvent, FALSE);

 // Build Internal IOCTL IRP

 PIRP Irp = IoBuildDeviceIoControlRequest(

  IoControlCode, dx->NextStackDevice,

  NULL, 0, // Input buffer

  NULL, 0, // Output buffer

  TRUE, &event, &IoStatus);

 // Get IRP stack location for next driver down (already set up)

 PIO_STACK_LOCATION NextIrpStack = IoGetNextIrpStackLocation(Irp);

 // Store pointer to the URB etc

 NextIrpStack->Parameters.Others.Argument1 = UrbEtc;

 NextIrpStack->Parameters.Others.Argument2 = (PVOID)Arg2;

 // Call the driver and wait for completion if necessary

 NTSTATUS status = IoCallDriver(dx->NextStackDevice, Irp);

 if (status == STATUS_PENDING) {

  KeWaitForSingleObject(&event, Suspended, KernelMode, FALSE, NULL);

  status = IoStatus.Status;

 }

 // return IRP completion status

 return status;

}

Other IRP Allocations

Other kernel calls can be used to allocate IRPs. IoBuildSynchronousFsdRequest builds a Read, Write, Flush, or Shutdown IRP that uses an event to signal completion in the same way as IoBuildDeviceIoControlRequest. IoBuildSynchronousFsdRequest must be called at PASSIVE_LEVEL IRQL.

IoBuildAsynchronousFsdRequest works asynchronously, as it does not use an event to signal its completion. Consequently, it can be called at or below DISPATCH_LEVEL. Note that the IRP must be freed using IoFreeIrp. A common way to do this is to attach a completion routine. It is OK to call IoFreeIrp here and then return STATUS_MORE_PROCESSING_REQUIRED.

There are two final macho ways of building IRPs. IoAllocateIrp allocates an IRP, while IoInitializeIrp makes an IRP out of some driver allocated memory. Be very careful to set up all IRP and IRP stack locations correctly. Both of these methods allow you specify the size of the IRP stack size. Use IoGetNextIrpStackLocation to get the first IRP stack location if you need to set up a completion routine. Call IoFreeIrp to free an IRP created by IoAllocateIrp.

The old DDK documentation wrongly says that you can call IoInitializeIrp to reuse an IRP allocated using IoAllocateIrp. You can use this function as long as you preserve the IRP AllocationFlags field, as shown in Chapter 23. In W2000, you can reuse an IRP created with IoAllocateIrp using IoReuseIrp.

Multiple USBDI Calls

If you were reading carefully, you will have noticed that the Call USBDI routine can only be called at PASSIVE_LEVEL. This means that it cannot be called from a StartIo routine, which runs at DISPATCH_LEVEL. UsbKbd makes its USB calls direct from its dispatch routines that run at PASSIVE_LEVEL.

In UsbKbd, it is possible for a user program to issue two overlapped read requests "simultaneously". This might easily result in the USB class drivers being sent two IRPs for processing at the same time. There is nothing in the documentation that says this is a problem. I suspect that it is not, as the USB class driver will almost certainly serialize requests.

If you feel that you ought to serialize your USBDI calls, you will have to use a StartIo routine. A single prebuilt IRP can be reused for each call. Chapter 23 shows how to build, use, and free such an IRP.

In many cases, it might be useful to send off a series of IRPs to the USB class drivers. As there will usually be a spare IRP queued up for processing, no incoming data will be lost.

Talking USB

Initializing a USB Device

There are several jobs that a USB client driver must do to initialize its connection to its device. The UsbKbd driver does these jobs in its Create IRP handler. However, most USB drivers will want to initialize their device when processing the Start Device Plug and Play IRP.

1. Check device enabled. Reset and enable the device, if necessary.

2. Select one interface in one of the configurations.

3. Possibly read other descriptors, such as the string-, class-, or vendor-specific descriptors.

4. Talk to your device to issue whatever commands are relevant, get device status, and initialize pipes.

Device Reset

Listing 21.3 shows how UsbKbd resets its device in routine UsbResetDevice when a Win32 program opens a handle to it. UsbGetPortStatus issues IOCTL_INTERNAL_USB_GET_PORT_STATUS to retrieve the port status bits. UsbResetDevice checks the USBD_PORT_CONNECTED and USBD_PORT_ENABLED bits, and calls UsbResetPort, if necessary. UsbResetPort simply issues IOCTL_INTERNAL_USB_RESET_PORT to the USB class drivers.

You may need to reset the port if some drastic communication problem has arisen with your device (e.g., if control transfers to the default pipe keep failing). However, if another pipe stalls, do not reset the port.

If a pipe stalls or USBDI detects a timeout, the pipe becomes halted. The USBD_HALTED macro detects this condition. All URBs linked to the current URB are cancelled. A halted pipe cannot accept any more transfers until a Reset Pipe URB is issued.

However, the default pipe can never be halted. If a timeout or stall occurs here, an error is reported, which is detectable using the USBD_ERROR macro, but not USBD_HALTED. The halt condition is cleared automatically to give transfers a chance of succeeding on this pipe. If they keep failing, you will have to reset the port.

Listing 21.3 Device reset routines

NTSTATUS UsbGetPortStatus(IN PUSBKBD_DEVICE_EXTENSION dx, OUT ULONG& PortStatus) {

 DebugPrintMsg("Getting port status");

 PortStatus = 0;

 NTSTATUS status = CallUSBDI(dx, &PortStatus, IOCTL_INTERNAL_USB_GET_PORT_STATUS);

 DebugPrint("Got port status %x", PortStatus);

 return status;

}

NTSTATUS UsbResetPort(IN PUSBKBD_DEVICE_EXTENSION dx) {

 DebugPrintMsg("Resetting port");

 NTSTATUS status = CallUSBDI(dx, NULL, IOCTL_INTERNAL_USB_RESET_PORT);

 DebugPrint("Port reset %x", status);

 return status;

}

NTSTATUS UsbResetDevice(IN PUSBKBD_DEVICE_EXTENSION dx) {

 ULONG PortStatus;

 NTSTATUS status = UsbGetPortStatus(dx, PortStatus);

 if (!NT_SUCCESS(status)) return status;

 // Give up if device not connected

 if (!(PortStatus & USBD_PORT_CONNECTED)) return STATUS_NO_SUCH_DEVICE;

 // Return OK if port enabled

 if (PortStatus & USBD_PORT_ENABLED) return status;

 // Port disabled so attempt reset

 status = UsbResetPort(dx);

 if (!NT_SUCCESS(status)) return status;

 // See if it is now working

 status = UsbGetPortStatus(dx, PortStatus);

 if (!NT_SUCCESS(status)) return status;

 if (!(PortStatus & USBD_PORT_CONNECTED) || !(PortStatus & USBD_PORT_ENABLED)) return STATUS_NO_SUCH_DEVICE;

 return status;

}

Issuing URBs

The Usb.cpp module contains many routines that build and send off URBs for processing by the USB class drivers. UsbGetDeviceDescriptor is used to retrieve the USB device descriptor. This routine is not essential in UsbKbd, but it is used to implement one of the IOCTLs that are available to user mode programs. UsbGetDeviceDescriptor allocates nonpaged memory for the device descriptor and puts the count of bytes transferred in its Size parameter. The routine that calls UsbGetDeviceDescriptor must free the memory using ExFreePool.

UsbGetDeviceDescriptor starts by allocating some nonpaged memory for the URB. The Get Descriptor URB function uses the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE function code. For this function code, the _URB_CONTROL_DESCRIPTOR_REQUEST structure is used.

Next, UsbGetDeviceDescriptor allocates memory for the device descriptor. Then, it calls UsbBuildGetDescriptorRequest to do the hard work of formatting the URB. The URB pointer and its size are passed as the first two parameters. The next parameter specifies the descriptor type, a device descriptor in this case. The following Index and LanguageId fields are only used when requesting configuration and string descriptors.

The following parameters to UsbBuildGetDescriptorRequest specify the descriptor buffer and its length. This buffer can be specified as a plain pointer to nonpaged memory. Alternatively, an MDL can be used. The final parameter is UrbLink, an optional link to a follow on URB.

UsbGetDeviceDescriptor sends off the built URB using CallUSBDI. It checks both the CallUSBDI NTSTATUS return status and the URB completion status.

The final job for UsbGetDeviceDescriptor is simply to save the count of bytes transferred. This was stored in the URB UrbControlDescriptorRequest.TransferBufferLength field. The USB memory is freed.

Listing 21.4 Getting a USB device descriptor

NTSTATUS UsbGetDeviceDescriptor(IN PUSBKBD_DEVICE_EXTENSION dx, OUT PUSB_DEVICE_DESCRIPT0R& deviceDescriptor, OUT ULONGS Size) {

 // Allocate memory for URB

 USHORT UrbSize = sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST);

 PURB urb = (PURB)ExAllocatePool(NonPagedPool, UrbSize);

 if (urb==NULL) {

  DebugPrintMsg("No URB memory");

  return STATUS_INSUFFICIENT_RESOURCES;

 }

 // Allocate memory for device descriptor

 ULONG sizeDescriptor = sizeof(USB_DEVICE_DESCRIPTOR);

 deviceDescriptor = (PUSB_DEVICE_DESCRIPTOR)ExAllocatePool(NonPagedPool, sizeDescriptor);

 if (deviceDescriptor==NULL) {

  ExFreePool(urb);

  DebugPrintMsg("No descriptor memory");

  return STATUS_INSUFFICIENT_RESOURCES;

 }

 // Build the Get Descriptor URB

 UsbBuildGetDescriptorRequest(urb, UrbSize,

  USB_DEVICE_QESCRIPTOR_TYPE, 0, 0, // Types, Index & LanguageId

  deviceDescriptor, NULL, sizeDescriptor, // Transfer buffer

  NULL); // Link URB

 // Call the USB driver

 DebugPrintMsg("Getting device descriptor");

 NTSTATUS status = CallUSBDI(dx, urb);

 // Check statuses

 if (!NT_SUCCESS(status) || !USBD_SUCCESS( urb->UrbHeader.Status)) {

  DebugPrint("status %x URB status %x", status, urb->UrbHeader.Status);

  status = STATUS_UNSUCCESSFUL;

 }

 // Remember count of bytes actually transferred

 Size = urb->UrbControlDescriptorRequest.TransferBufferLength;

 ExFreePool(urb);

 return status;

}

Selecting an Interface

To start using a device, your driver must select one interface within one configuration. This is more of a job than you might expect. Routine UsbSelectConfiguration performs these steps, as shown in Listing 21.5.

1. Get the configuration, interface, endpoint descriptors, etc.

2. Find the appropriate interface descriptor.

3. Issue a select configuration URB.

4. Save the configuration handle and handles to any pipes that you use.

UsbGetConfigurationDescriptors (not listed) is used to retrieve all the descriptors that are needed. This routine issues a Get Descriptor request twice. The first call returns just the basic configuration descriptor. The wTotalLength field in there tells you how much memory to allocate to retrieve all the associated descriptors.

USBD_ParseConfigurationDescriptorEx is used to find an interface. The parameters to this routine specify the criteria that must match. In this case UsbKbd is not interested in matching the interface number or alternate settings, but is interested in the device class. The HID device class is 3 (USB_DEVICE_CLASS_HUMAN_INTERFACE). To match a HID keyboard the subclass and protocol interface descriptor fields must both be 1.

If USBD_ParseConfigurationDescriptorEx finds a matching interface, it returns the interface descriptor pointer. The next task is to build a suitable Select Configuration URB. The helper function USBD_CreateConfigurationRequestEx allocates and fills this URB. UsbSelectConfiguration specifies an array of USBD_INTERFACE_LIST_ENTRY structures as an input to this routine, with a NULL InterfaceDescriptor field indicating the end of the list. Each structure specifies the interface descriptor. When the Select Configuration URB has been run, the Interface field points to valid data.

The Select Configuration URB is then issued. This may fail if there is not enough USB bandwidth available. If it works, a configuration handle is returned. You can use this to select a different alternate interface setting. UsbKbd stores the configuration handle in its device extension, but does not use it.

UsbKbd does need to store the pipe handle for the HID keyboard's interrupt pipe. For a HID USB keyboard, this is the first and only pipe in the USBD_INTERFACE_INFORMATION structure. UsbKbd makes DebugPrint calls to display other potentially useful information. UsbKbd finally frees the URB and configuration descriptor memory.

Listing 21.5 Selecting a configuration and interface

NTSTATUS UsbSelectConfiguration(IN PUSBKBD_DEVICE_EXTENSION dx) {

 dx->UsbPipeHandle = NULL;

 // Get all first configuration descriptors

 PUSB_CONFIGURATIONDESCRIPTOR Descriptors = NULL;

 ULONG size;

 NTSTATUS status = UsbGetConfigurationDescriptors(dx, Descriptors, 0, size);

 if (!NT_SUCCESS(status)) {

  DebugPrint("UsbGetConfigurationDescriptors failed %x", status);

  FreeIfAllocated(Descriptors);

  return status;

 }

 // Search for an interface with HID keyboard device class

 PUSB_INTERFACE_DESCRIPTOR id = USBD_ParseConfigurationDescriptorEx(Descriptors, Descriptors,

  -1, –1, // Do not search by InterfaceNumber or AlternateSetting

  3, 1, 1); // Search for a HID device, boot protocol, keyboard

 if (id==NULL) {

  DebugPrintMsg("No matching interface found");

  FreeIfAllocated(Descriptors);

  return STATUS_NO_SUCH_DEVICE;

 }

 // Build list of interfaces we are interested in

 USBD_INTERFACE_LIST_ENTRY ilist[2];

 ilist[0].InterfaceDescriptor = id;

 ilist[0].Interface = NULL; // Will point to

                            // urb->UrbUsbSelectConfiguration.Interface

 ilist[1].InterfaceDescriptor = NULL;

 // Create select configuration URB

 PURB urb = USBD_CreateConfigurationRequestEx(Descriptors, ilist);

 // Call the USB driver

 DebugPrintMsg("Select ing configuration");

 status = CallUSBDI(dx, urb);

 // Check statuses

 if (!NT_SUCCESS(status) || !USBD_SUCCESS( urb->UrbHeader.Status)) {

  DebugPrint("status %x URB status %x", status, urb->UrbHeader.Status);

  status = STATUS_UNSUCCESSFUL;

 } else {

  // Select config worked

  DebugPrintMsg("Select configuration worked");

  dx->UsbConfigurationHandle = urb->UrbSelectConfiguration.ConfigurationHandle;

  // Find pipe handle of first pipe,

  // ie interrupt pipe that returns input HID reports

  PUSBD_INTERFACE_INFORMATION InterfaceInfo = &urb->UrbSelectConfiguration.Interface;

  DebugPrint("interface Class %d NumberOfPipes %d", InterfaceInfo->Class, InterfaceInfo->NumberOfPipes);

  if (InterfaceInfo->NumberOfPipes>0) {

   PUSBD_PIPE_INFORMATION pi = &InterfaceInfo->Pipes[0];

   dx->UsbPipeHandle = pi->PipeHandle;

   DebugPrint("PipeHandle = %x", dx->UsbPipeHandle);

   DebugPrint("Pipes[0] EndpointAddress %2x"

    "Interval %dms PipeType %d MaximumTransferSize %c",

    pi->EndpointAddress, pi->Interval, pi->PipeType, pi->MaximumTransferSize);

  }

  if (dx->UsbPipeHandle==NULL) status = STATUS_UNSUCCESSFUL;

 }

 FreeIfAllocated(urb);

 FreeIfAllocated(Descriptors);

 return status;

}

Other Initialization

As mentioned earlier, having selected your configuration and interface, there may be some more steps needed to initialize your USB peripheral. You may want to read other descriptors. The UsbKbd Create IRP handler shows how this might be done by getting the first string descriptor. This in fact just returns the language ID that the device supports. You will have to issue further Get Descriptor requests to get each String Descriptor. The UsbGetSpecifiedDescriptor routine in Usb.cpp can be used to get any descriptor. This routine is also used by the IOCTL_USBKBD_GET_SPECIFIED_DESCRIPTOR handler. The UsbKbdTest test program issues this IOCTL to get the HID Descriptor.

The UsbKbd Create IRP handler, UsbKbdCreate, also retrieves some general information about the USB bus, such as the amount of bandwidth used and the host controller device name. The required Internal IOCTLs are only implemented in Windows 2000, so preprocessor directives are used to remove the body of the function that does this job, UsbGetUsbInfo, when compiled for Windows 98.

Most USB device drivers will now want to talk to their devices over control or other pipes. Shortly, I describe how to perform such transfers.

Deselecting a Configuration

When a driver wants to stop accessing its device, it should deselect its configuration. In UsbKbd, the Close file handle IRP handler does this job. The UsbDeselectConfiguration routine uses UsbBuildSelectConfigurationRequest to build a Select Configuration URB with a NULL configuration descriptor. This disables the configuration and its interfaces.

Interrupt Transfers

The specification for UsbKbd says that it reports raw keyboard data in response to ReadFile Win32 requests. It also must implement a time-out, which defaults to 10 seconds.

Before I describe how to handle the Read IRPs, I must discuss how a USB HID keyboard produces data. The keyboard responds to USB Interrupt transfers with an 8-byte data report. The exact format of this block is discussed in the next chapter on Human Input Devices (HID).

A keyboard report is produced whenever a keypress or release occurs. A report is also produced regularly even if no keypresses occur. This is what happens on the USB bus. The USB class drivers initiate an Interrupt transfer regularly. My USB keyboard specifies that interrupt transfers should occur every 8ms. However, USB HID keyboards implement an idle rate. If there are no state changes during the idle period, the keyboard NAKs each Interrupt request. At the end of the idle period (every 500ms or so), the keyboard returns data regardless. The idle rate can be read and changed using class specific control transfers on the default pipe[53].

UsbKbd issues a Do Bulk or Interrupt Transfer URB to receive any keyboard reports. Keyboard reports are not "saved up" ready to fulfil any interrupt transfer requests. It is not clear whether the USB class drivers simply do not do any transfers, or whether they do the transfers but just dump the data. Any keypresses before an interrupt transfer is requested are ignored. I understand that the Microsoft keyboard drivers always try to leave two Interrupt transfer requests outstanding; the hope is that this should ensure that no keypresses are lost.

It turns out that the 8-byte keyboard report is all zeroes if no keys are pressed. The UsbKbd Read IRP handler ignores any keyboard reports that are all zero. A real driver would return read data when any key is pressed or released. A higher-level driver would have to implement an auto-repeat feature.

I can finally describe the Interrupt transfer routine, UsbDoInterruptTransfer, shown in Listing 21.6. This takes a buffer as input that must be at least 8 bytes long. UsbDoInterruptTransfer first checks that the USB pipe handle is available and that the input buffer is suitable.

In the same way as usual, memory is allocated for the URB, a _URB_BULK_OR_INTERRUPT_TRANSFER structure. UsbDoInterruptTransfer then loops until a non-zero keyboard report is returned, or until a time-out or another error occurs. UsbBuildInterruptOrBulkTransferRequest is used to build the URB each time[54]. UsbDoInterruptTransfer calls the USB class drivers in the usual way.

The time-out is checked using the KeQueryTickCount call[55]. This returns a ULONG count of "timer interrupts" since the operating system started. The timer interrupt interval is not the same in Windows 98 and Windows 2000. Use KeQueryTimeIncrement to find out this interval in units of 100ns. In both cases, there are just over 100 timer ticks per second.

UsbDoInterruptTransfer remembers the tick count before the first keyboard Interrupt request is generated. After each Interrupt request completes, it checks the tick count again, and gives up after the specified time-out.

UsbKbd does not handle IRP cancelling or the Cleanup routine. UsbKbd relies on the time-out to complete Read IRPs. If you do implement IRP cancelling, then you will need to use the Abort Pipe URB request to cancel all requests on the specified port.

Listing 21.6 Doing interrupt transfers

NTSTATUS UsbDoInterruptTransfer(IN PUSBKBD_DEVICE_EXTENSION dx, IN PVOID UserBuffer, ULONG& UserBufferSize) {

 // Check we're selected

 if (dx->UsbPipeHandle==NULL) return STATUS_INVALID_HANDLE;

 // Check input parameters

 ULONG InputBufferSize = UserBufferSize;

 UserBufferSize = 0;

 if (UserBuffer==NULL || InputBufferSize<8) return STATUS_INVALID_PARAMETER;

 // Keyboard input reports are always 8 bytes

 long NTSTATUS status = STATUS_SUCCESS;

 ULONG OutputBufferSize = 8;

 // Allocate memory for URB

 USHORT UrbSize = sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER);

 PURB urb = (PURB)ExAllocatePool(NonPagedPool, UrbSize);

 if (urb==NULL) {

  DebugPrintMsg("No URB memory");

  return STATUS_INSUFFICIENT_RESOURCES;

 }

 // Remember when we started

 // Get start tick count and length of tick in 100ns units

 LARGE_INTEGER StartTickCount;

 KeQueryTickCount(&StartTickCount);

 ULONG UnitsOf100ns = KeQueryTimeIncrement();

 // Loop until non-zero report read, error, bad length, or timed out

 while(true) {

  // Build Do Bulk or Interrupt transfer request

  UsbBuildInterruptOrBulkTransferRequest(urb, UrbSize, dx->UsbPipeHandle, UserBuffer, NULL, OutputBufferSize, USBD_TRANSFER_DIRECTION_IN, NULL);

  // Call the USB driver

  status = CallUSBDI(dx, urb);

  // Check statuses

  if (!NT_SUCCESS(status) || !USBD_SUCCESS( urb->UrbHeader.Status)) {

   DebugPrint("status %x URB status %x", status, urb->UrbHeader.Status);

   status = STATUS_UNSUCCESSFUL;

   break;

  }

  // Give up if count of bytes transferred was not 8

  if (urb->UrbBulkOrInterruptTransfer.TransferBufferLength!= OutputBufferSize) break;

  // If data non-zero then exit as we have a keypress

  __int64* pData = (__int64 *)UserBuffer;

  if (*pData!=0i64) break;

  // Check for time-out

  LARGE_INTEGER TickCountNow;

  KeQueryTickCount(&TickCountNow);

  ULONG ticks = (ULONGKTickCountNow.QuadPart – StartTickCount.QuadPart);

  if (ticks*UnitsOf100ns/10000000 >= dx->UsbTimeout) {

   DebugPrint("Time-out %d 100ns", ticks*UnitsOf100ns);

   status = STATUS_NO_MEDIA_IN_DEVICE;

   break;

  }

 }

 UserBufferSize = urb->UrbBulkOrInterruptTransfer.TransferBufferLength;

 if (NT_SUCCESS(status)) {

  PUCHAR bd = (PUCHAR)UserBuffer;

  DebugPrint("Transfer data %2x %2x %2x %2x %2x %2x %2x %2x", bd[0], bd[1], bd[2], bd[3], bd[4], bd[5], bd[6], bd[7]);

 }

 ExFreePool(urb);

 return status;

}

Control Transfers

The UsbKbd driver lets its controlling application modify the state of the keyboard LEDs. This lets me illustrate how to do Control transfers on the default pipe to endpoint zero. The Write IRP handler sends the first byte of the write buffer to the keyboard as a HID "output report". Again, the next chapter fully defines the format of this report and the SET_REPORT command. The lower three bits of the report correspond to the NumLock, CapsLock, and ScrollLock keys.

The Write IRP handler calls UsbSendOutputReport to send the output report. As per usual, it allocates a suitable URB. The UsbBuildVendorRequest helper function is used to fill this URB. Control transfers to the default pipe can take many shapes and forms. First, there are "class" and "vendor" types. For each type, you can specify whether the destination is the "device", "interface", "endpoint", or "other".

The HID Specification tells us to send a "class" control transfer request to the "interface". The call to UsbBuildVendorRequest, therefore, uses the URB_FUNCTION_CLASS_INTERFACE URB function code. The TransferFlags parameter specifies the direction of the data transfer; set the USBD_TRANSFER_DIRECTION_IN flag for input transfers. The Request, Value, and Index parameters are set as instructed by the HID Specification. Finally, the output data buffer is given.

The URB is then sent off to the class drivers. Apart from noting the URB status and freeing the URB memory, there is nothing else to do.

The UsbGetIdleRate routine in Usb.cpp shows how to input data using a control transfer over the default pipe. This issues a GET_IDLE request. The current keyboard idle rate is printed in a DebugPrint trace statement.

Listing 21.7 Doing an output control transfer

const UCHAR SET_REPORT = 0x09;

NTSTATUS UsbSendOutputReport(IN PUSBKBD_DEVICE_EXTENSION dx, IN UCHAR OutputData) {

 // Allocate memory for URB

 USHORT UrbSize = sizeof(struct _URB_CONTROL_VENDOR_OR_CLASS_REQUEST);

 PURB urb = (PURB)ExAllocatePool(NonPagedPool, UrbSize);

 if (urb==NULL) {

  DebugPrintMsg("No URB memory");

  return STATUS_INSUFFICIENT_RESOURCES;

 }

 // Build URB to send Class interface control request on Default pipe

 UsbBuildVendorRequest(urb,

  URB_FUNCTION_CLASS_INTERFACE, UrbSize,

  USBD_TRANSFER_DIRECTION_OUT, // Direction out

  0, // Reserved bits

  SET_REPORT, // Request

  0x0200, // Output report type, Report id zero

  0, // interface index

  &OutputData, NULL, 1, // Output data

  NULL);

 // Call the USB driver

 DebugPrintMsg("Sending set report");

 NTSTATUS status = CallUSBDI(dx, urb);

 // Check statuses

 if (!NT_SUCCESS(status) || !USBD_SUCCESS(urb->UrbHeader.Status)) {

  DebugPrint("status %x URB status %x", status, urb->UrbHeader.Status);

  status = STATUS_UNSUCCESSFUL;

 }

 ExFreePool(urb);

 return status;

}

Other Issues

The UsbGetStatuses routine shows how to read status words from the device, interface, and endpoint using the Get Status URB. These words are bundled into a 6-byte buffer in response to the UsbKbd IOCTL_USBKBD_GET_STATUSES request.

The UsbKbd IOCTL_USBKBD_GET_FRAME_INFO request is handled in the UsbGetFrameInfo routine. UsbGetFrameInfo uses the Get Current Frame Number and Get Frame Length URBs to find out the required information. Both these URBs are built by hand, as there are no UsbBuild…Request helper macros. The frame numbers returned are 32-bit ULONGs, not the USB SOF number that goes from 0-0x7FF.

Testing UsbKbd

The Win32 UsbKbdTest program puts the UsbKbd driver through its paces. Obviously, you must have a USB-enabled PC and a USB keyboard to try out this code. For the benefit of those who do not, I am reproducing both the UsbKbdTest output and the UsbKbd DebugPrint trace output in Listings 21.8 and 21.9.

You will first have to "install" the UsbKbd.sys driver by copying it into the Windows System32\drivers directory to replace HidUsb.sys. Do not forget to put the old HidUsb.sys back when you are finished.

The UsbKbdTest program does the following jobs.

1. Open a handle to the first driver that supports the USBKBD_GUID device interface.

2. Read the device descriptor.

3. Read the configuration descriptor, interface descriptor, etc. The UsbKbdTest output has been amended in Listing 21.8 to show each descriptor.

4. Read the HID report descriptor, type 0x22.

5. Set the read time-out to 15 seconds.

6. Keep reading keyboard input data until the Esc key is pressed (code 0x29). The output listing has been annotated to show when I have pressed these keys: Ctr+Alt+Del, then A, B, C and Esc.

7. Write a series of bit combinations to flash the keyboard LEDs, with a ⅓-second delay between changes.

8. Read the device, interface, and endpoint status words.

9. Read the frame length and frame numbers.

10. Close the file handle.

Armed with all the relevant specifications, you can decode what all the descriptors and keyboard data mean. The UsbKbd trace output lists some more useful information.

Listing 21.8 UsbKbdTest output

Test 1

Symbolic link is \\?\usb#vid_046a&pid_0001#7&4#{c0cf0646-5f6e-11d2-b677-00c0dfe4c1f3}

     Opened OK

Test 2

     Device descriptor is 12 01 00 01 00 00 00 08 6A 04 01 00 05 03 00 00 00 01

Test 3

     Configuration descriptors are

         09 02 22 00 01 01 00 A0 32 Configuration descriptor

         09 04 00 00 01 03 01 01 00 Interface descriptor

         07 05 81 03 08 00 08       Endpoint descriptor

         09 21 00 01 00 01 22 3F 00 HID descriptor

Test 4

     HID Report descriptor is 05 01 09 06 A1 01 05 07 19 E0 29 E7 15 00 25 01 75 01 95 08 81 02 75 08 95 01 81 01 05 08 19 01 29 03 75 01 95 03 91 02 75 05 95 01 91 01 05 07 19 00 29 65 15 00 25 65 75 08 95 06 81 00 C0

Test 5

     Read time-out set

Test 6

     Kbd report 1 0  0 0 0 0 0 0 Ctrl

     Kbd report 5 0  0 0 0 0 0 0 Ctrl+Alt

     Kbd report 5 0 63 0 0 0 0 0 Ctrl+Alt+Del

     Kbd report 4 0  0 0 0 0 0 0 Alt

     Kbd report 0 0  4 0 0 0 0 0 A

     Kbd report 0 0  5 0 0 0 0 0 B

     Kbd report 0 0  6 0 0 0 0 0 C

     Kbd report 0 0 29 0 0 0 0 0 Esc

Test 7

     Wrote  1 OK

     Wrote  2 OK

     Wrote  3 OK

     Wrote  4 OK

     Wrote  5 OK

     Wrote  6 OK

     Wrote  7 OK

     Wrote ff OK

     Wrote  0 OK

Test 8

     Statuses are 0 0 0

Test 9

     FrameLength 11999 bits. FrameNumber 1847715. FrameAlterNumber 1847715

Test 10

     CloseHandle worked

Listing 21.9 UsbKbdTest corresponding DebugPrint trace output in W2000

CreateFile

16:34:09.567 Create File is

16:34:09.567 USBDI version 00000200 Supported version 00000100

16:34:09.567 Getting port status

16:34:09.567 Got port status 00000003

16:34:09.567 Getting basic configuration descriptor

16:34:09.567 Got basic config descr. MaxPower 50 units of 2mA

16:34:09.567 Getting full configuration descriptors

16:34:09.577 Selecting configuration

16:34:09.677 Select configuration worked

16:34:09.577 interface Class 3 NumberOfPipes 1

16:34:09.577 PipeHandle = 80973FC0

16:34:09.577 Pipes[0] EndpointAddress 81 Interval 8ms PipeType 3 MaximumTransferSize 4096

16:34:09.577 Getting bus info

16:34:09.577 Bus info: TotalBandwidth 12000, ConsumedBandwidth 0 and ControllerNameLength 32

16:34:09.577 Controller name is \DosDevices\HCD0

16:34:09.577 Getting descriptor type 03

16:34:09.577 No string descriptors

16:34:09.577 Sending Get Idle request

16:34:09.577 Idle rate is 125 units of 4ms

Get device descriptor using IOCTL_USBKBD_GET_DEVICE_DESCRIPTOR

16:34:09.577 DeviceIoControl: Control code 00222008 InputLength 0 OutputLength 50

16:34:09.577 Getting device descriptor 16:34:09.577 DeviceIoControl: 18 bytes written

Get all configuration descriptors using IOCTL_USBKBD_GET_CONFIGURATION_DESCRIPTORS

16:34:09.587 DeviceIoControl: Control code 0022200C InputLength 0 OutputLength 500

16:34:09.587 Getting basic configuration descriptor

16:34:09.587 Got basic config descr. MaxPower 50 units of 2mA

16:34:09.587 Getting full configuration descriptors

16:34:09.587 DeviceIoControl: 34 bytes written

Get HID Report Descriptor using IOCTL_USBKBD_GET_SPECIFIED_DESCRIPTOR

16:34:09.597 DeviceIoControl: Control code 00222010 InputLength 8 OutputLength 500

16:34:09.597 Getting descriptor type 22

16:34:09.597 DeviceIoControl: 63 bytes written

Set read timeout using IOCTL_USBKBD_SET_READ_TIMEOUT

16:34:09.617 DeviceIoControl: Control code 00222004 InputLength 4 OutputLength 0

16:34:09.617 USB timeout set to 15

16:34:09.617 DeviceIoControl: 0 bytes written

Read Ctrl+Alt+Del key presses

16:34:09.617 Read 8 bytes from file pointer 0

16:34:13.022 Transfer data 04 00 00 00 00 00 00 00

16:34:13.022 Read: 00000000 8 bytes returned

16:34:13.022 Read 8 bytes from file pointer 0

16:34:13.112 Transfer data 05 00 00 00 00 00 00 00

16:34:13.112 Read: 00000000 8 bytes returned

16:34:13.112 Read 8 bytes from file pointer 0

16:34:13.222 Transfer data 05 00 63 00 00 00 00 00

16:34:13.222 Read: 00000000 8 bytes returned

16:34:13.292 Read 8 bytes from file pointer 0

16:34:13.993 Transfer data 04 00 00 00 00 00 00 00

16:34:13.993 Read: 00000000 8 bytes returned

Read A, B, C, Esc key presses

16:34:14.023 Read 8 bytes from file pointer 0

16:34:16.757 Transfer data 00 00 04 00 00 00 00 00

16:34:16.757 Read: J0000000 8 bytes returned

16:34:16.777 Read 8 bytes from file pointer 0

16:34:17.128 Transfer data 00 00 05 00 00 00 00 00

16:34:17.128 Read: 00000000 8 bytes returned

16:34:17.158 Read 8 bytes from file pointer 0

16:34:17.609 Transfer data 00 00 06 00 00 00 00 00

16:34:17.609 Read: 00000000 8 bytes returned

16:34:17.639 Read 8 bytes from file pointer 0

16:34:17.859 Transfer data 00 00 29 00 00 00 00 00

16:34:17.859 Read: 00000000 8 bytes returned

Write different bit combinations to flash the keyboard LEDs

16:34:17.969 Write 1 bytes from file pointer 0

16:34:17.969 Sending set report

16:34:17.969 Write: 1 bytes written

16:34:21.124 Write 1 bytes from file pointer 0

16:34:21.124 Sending set report

16:34:21.144 Write: 1 bytes written Get statuses using IOCTL_USBKBD_GET_STATUSES

16:34:21.524 DeviceIoControl: Control code 00222014 InputLength 0 OutputLength 6

16:34:21.524 Getting device status

16:34:21.524 Getting interface status

16:34:21.524 Getting endpoint status

16:34:21.524 DeviceIoControl: 6 bytes written

Read frame length and numbers using IOCTL_USBKBD_GET_FRAME_INFO

16:34:21.524 DeviceIoControl: Control code 00222018 InputLength 0 OutputLength 12

16:34:21.524 Getting current frame number

16:34:21.524 FrameNumber 912595

16:34:21.524 Getting frame info

16:34:21.524 FrameLength 11999 FrameAlterNumber 912595

16:34:21.524 DeviceIoControl: 12 bytes written

CloseHandle

16:34:21.634 Close

16:34:21.634 Deselecting configuration

USBDI Structure Reference

This section provides a reference for the USB Driver Interface (USBDI). The basic URB structure has been covered in enough detail in earlier sections of this chapter. Therefore, I first describe various other USBDI structures. Then, I go on to detail each of the URB function codes, the corresponding URB structures and any useful build routines.

Structures

Device Descriptor

A device descriptor is returned in a USB_DEVICE_DESCRIPTOR structure. Table 21.4 shows the fields of interest in the device descriptor. The device class, subclass, and protocol fields are not used by Windows and are often zeroes. However, they may be used to indicate which new common class specification features are used.

Table 21.4 USB DEVICE DESCRIPTOR fields

bDeviceClass bDeviceSubClass bDeviceProtocolThe USB class codes
idVendor idProduct bcdDeviceVendor, product, and version numbers
iManufacturer iProduct iSerialNumberIndexes into the string descriptor for the relevant strings
bMaxPacketSize0The maximum packet size for Endpoint 0
bNumConfigurationsThe number of configurations

Configuration Descriptor

A configuration descriptor is returned in a USB_CONFIGURATION_DESCRIPTOR structure, with fields as shown in Table 21.5.

A request for a configuration descriptor will also get any other descriptors associated with this configuration (interface-, endpoint-, class-, and vendor-defined descriptors), if there is enough room in your buffer. You will usually get the configuration descriptor twice. First, get the basic USB_CONFIGURATION_DESCRIPTOR structure. The wTotalLength field tells you how big a buffer to allocate to retrieve all the descriptors in your next get configuration request.

Table 21.5 USB CONFIGURATION DESCRIPTOR fields

wTotalLengthTotal length of all data for the configuration
bNumInterfacesNumber of interfaces
iConfigurationConfiguration number
bmAttributesbit 5 set: supports remote wake up
bit 6 set: self-powered
bit 7 set: powered from bus
maxPowerMaximum power required, in 2mA units

Interface Descriptor

An interface descriptor is returned in a USB_INTERFACE_DESCRIPTOR structure, with fields shown Table 21.6. Interface descriptors are returned as a part of a get configuration descriptor request.

Table 21.6 USB_INTERFACE DESCRIPTOR fields

bInterfaceNumberInterface number
bAlternateSettingAlternate setting
bNumEndpointsNumber of endpoints
bInterfaceClass bInterfaceSubClass bInterfaceProtocolUSB class codes
iInterfaceIndex of string descriptor describing the interface

Interface Selection Structures

Various structures are needed when you select a configuration. An array of USB_INTERFACE_LIST_ENTRY structures, shown in Table 21.7, is passed to USBD_CreateConfigurationRequestEx. The InterfaceDescriptor field points to the interface descriptor that you want enabled in the configuration. When the Select Configuration URB has completed, the Interface pointer is valid and refers to valid data.

Table 21.7 USB INTERFACE LIST ENTRY fields

InterfaceDescriptorPoints to interface descriptor
InterfaceUSB_INTERFACE_INFORMATION pointer

The Select Configuration URB fills a USB_INTERFACE_INFORMATION structure for each interface that it enables. Table 21.8 shows this structure, whose size increases as there are more pipes.

Table 21.8 USB INTERFACE INFORMATION fields

InterfaceNumberInterface number
AlternateSettingAlternate setting
Class Subclass ProtocolUSB class codes
NumberOfPipesNumber of pipes
PipeInformationArray of USB_PIPE_INFORMATION structures

Table 21.9 shows the USB_PIPE_INFORMATION structure. All fields apart from MaximumTransferSize and PipeFlags fields are filled in by the Select Configuration URB.

Table 21.9 USB_PIPE INFORMATION fields

MaximumPacketSizeMaximum packet size
EndpointAddressEndpoint number The top bit is set for input pipes.
IntervalPolling interval in ms for Interrupt pipes
PipeTypeUsbdPipeTypeControl UsbdPipeTypeIsochronous UsbdPipeTypeBulk UsbdPipeTypeInterrupt
PipeHandleHandle for later USBDI calls
MaximumTransferSizeMaximum transfer size
PipeFlagsFlags

String Descriptor

A device may optionally provide string descriptors. Each string descriptor contains just one Unicode string, so the driver must request each string one by one. String index zero is special. Instead of a string, it contains a list of two-byte language ID codes. Language ID 0x0009 is usually used for generic English.

As has been seen, various other descriptors contain string index numbers. If these numbers are non-zero, there is an associated string. When you request a string descriptor using the GetDescriptor URB, you must specify both the string index and the language ID that you want.

A USB_STRING_DESCRIPTOR structure simply has one useful field, bString, that contains the Unicode string for which you asked. The maximum string length (MAXIMUM_USB_STRING_ LENGTH) is 255 characters. When you request string index zero, bString instead contains the supported language ID.

Isochronous Packet Descriptor

The USBD_ISO_PACKET_DESCRIPTOR structure is used to describe an isochronous transfer packet. An isochronous transfer request URB has a single transfer buffer that may contain several individual packets.

For writes, set the Offset field of each packet descriptor to indicate the offset into this buffer of this packet.

For reads, the packet descriptor Length field is filled with the number of bytes read and the Status field returns the status of this transfer packet.

USBDI URB Reference

URB Setup Functions

All these URBs use standard USB control transfers over the default pipe. I will later explain the details of how to set or clear features, or do class or vendor defined control transfers over the default pipe.

Get Descriptor

Function codes URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE

Build routine UsbBuildGetDescriptorRequest

URB Structure _URB_CONTROL_DESCRIPTOR_REQUEST

Use this function to get any descriptor from a device. The DescriptorType URB field determines which descriptor is retrieved. A suitably sized buffer must be provided. If asking for a configuration descriptor, specify an appropriate value for the URB Index field. If you are getting a string descriptor, specify a LanguageId field, as well.

If you ask for a configuration descriptor, you also get all the associated interface, endpoint, and class– and vendor-defined descriptors.

Other Get/Set Descriptors

Function codes URB_FUNCTION_GET_DESCRIPTOR_FROM_ENDPOINT URB_FUNCTION_SET_DESCRIPTOR_TO_ENDPOINT URB_FUNCTION_SET_DESCRIPTOR_TO_OTHER

Build routine none available

URB Structure _URB_CONTROL_DESCRIPTOR_REQUEST

This function gets or sets other descriptors.

Get Status

Function codes URB_FUNCTION_GET_STATUS_FROM_DEVICE URB_FUNCTION_GET_STATUS_FROM_INTERFACE URB_FUNCTION_GET_STATUS_FROM_ENDPOINT URB_FUNCTION_GET_STATUS_FROM_OTHER

Build routine UsbBuildGetStatusRequest

URB Structure _URB_CONTROL_GET_STATUS_REQUEST

This function gets a status word.

The device status word has self-powered and remote wakeup bits (USB_GETSTATUS_SELF_POWERED and USB_GETSTATUS_REMOTE_WAKEUP_ENABLED are the bit masks). The interface status currently has no bits defined. The endpoint status word has its bit zero set if the Halt feature has been set.

Select Configuration

Function codes URB_FUNCTION_SELECT_CONFIGURATION

Build routineUSBD_CreateConfigurationRequestEx UsbBuildSelectConfigurationRequest

URB Structure _URB_SELECT_CONFIGURATION

Use USBD_CreateConfigurationRequestEx to select a configuration and an interface, as described earlier.

If deselecting a configuration, use the simpler UsbBuildSelectConfigurationRequest to pass a NULL configuration descriptor.

Get Configuration

Function codes URB_FUNCTION_GET_CONFIGURATION

Build routine none available

URB Structure _URB_CONTROL_GET_CONFIGURATION_REQUEST

This function gets the current configuration descriptors.

Select Alternate Interface

Function codes URB_FUNCTION_SELECT_INTERFACE

Build routine UsbBuildSelectInterfaceRequest

URB Structure _URB_SELECT_INTERFACE

Use this URB function code to select an interface's alternate setting. Pass the configuration handle, the interface number, and the alternate setting.

Changing to an alternate interface setting discards any queued data on the interface endpoints.

Get Interface

Function codes URB_FUNCTION_GET_INTERFACE

Build routine none available

URB Structure _URB_CONTROL_GET_INTERFACE_REQUEST

This function gets the current alternate interface setting for an interface in the current configuration.

Reset Pipe

Function codes URB_FUNCTION_RESET_PIPE

Build routine none available

URB Structure _URB_HEADER

This function can clear a stall condition on the pipe with the given PipeHandle.

URB Transfer Functions

Do Control Transfer

Function codes URB_FUNCTION_CONTROL_TRANSFER

Build routine none available

URB Structure _URB_CONTROL_TRANSFER

This function can transmit or receive data on a control pipe with the given PipeHandle. Fill the SetupPacket 8-byte array with the information for the SETUP packet. The TransferBuffer fields specify the extra data. The USBD_TRANSFER_DIRECTION_IN bit in the TransferFlags field specifies the direction in which the extra data flows. Specify the USBD_SHORT_TRANSFER_OK flag bit if a short input transfer is acceptable.

Do not use his function for transfers over the default pipe.

Do Bulk or Interrupt Transfer

Function codes URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER

Build routine UsbBuildInterruptOrBulkTransferRequest

URB Structure _URB_BULK_OR_INTERRUPT_TRANSFER

Use this function to transmit or receive data on a bulk pipe, or receive data on an interrupt pipe. Specify the pipe using the PipeHandle field. The TransferBuffer fields specify the data. The USBD_TRANSFER_DIRECTION_IN bit in the TransferFlags field specifies the direction in which the data flows. Specify the USBD_SHORT_TRANSFER_OK flag bit if a short input transfer is acceptable.

Do Isochronous Transfer

Function codes URB_FUNCTION_ISOCH_TRANSFER

Build routine none available

URB Structure _URB_ISOCH_TRANSFER

Use this function to request an isochronous transfer. The TransferBuffer fields specify the data. The data block is split into one or more packets. The number of packets is given in NumberOfPackets. IsoPacket is an array of USBD_ISO_PACKET_DESCRIPTOR structures. Each of these specifies where a packet is in the transfer buffer (i.e., the Offset into the buffer, the Length, and the Status on return of the packet).

GET_ISO_URB_SIZE returns the number of bytes required to hold an isochronous request of the given number of packets.

If the START_ISO_TRANSFER_ASAP bit is set in TransferFlags field, the transfer is started as soon as possible. Otherwise, specify the 32-bit ULONG StartFrame number on which you want the transfer to start, within 1,000 frames of the current frame. The USBD_TRANSFER_ DIRECTION_IN flag bit specifies the direction of data transfer.

Cancel

Function codes URB_FUNCTION_ABORT_PIPE

Build routine none available

URB Structure _URB_PIPE_REQUEST

This function cancels all requests on the pipe with the given PipeHandle.

URB Default Pipe Functions

Set/Clear Feature

Function codes URB_FUNCTION_SET_FEATURE_TO_DEVICE URB_FUNCTION_SET_FEATURE_TO_INTERFACE URB_FUNCTION_SET_FEATURE_TO_ENDPOINT URB_FUNCTION_SET_FEATURE_TO_OTHER URB_FUNCTION_CLEAR_FEATURE_TO_DEVICE URB_FUNCTION_CLEAR_FEATURE_TO_INTERFACE URB_FUNCTION_CLEAR_FEATURE_TO_ENDPOINT URB_FUNCTION_CLEAR_FEATURE_TO_OTHER

Build routine UsbBuildFeatureRequest

URB Structure _URB_CONTROL_FEATURE_REQUEST

This function sets or clears a feature, in a control transfer over the default pipe. Specify a feature code.

Vendor/Class

Function codes URB_FUNCTION_VENDOR_DEVICE URB_FUNCTION_VENDOR_INTERFACE URB_FUNCTION_VENDOR_ENDPOINT URB_FUNCTION_VENDOR_OTHER URB_FUNCTION_CLASS_DEVICE URB_FUNCTION_CLASS_INTERFACE URB_FUNCTION_CLASS_ENDPOINT URB_FUNCTION_CLASS_OTHER

Build routine UsbBuildVendorRequest

URB Structure _URB_CONTROL_VENDOR_OR_CLASS_REQUEST

Use this function to issue a vendor or class-specific command to a device, interface, endpoint, or other device-defined target, in a control transfer over the default pipe.

The TransferBuffer fields specify the data. The USBD_TRANSFER_DIRECTION_IN bit in the TransferFlags field specifies the direction in which the data flows. Specify the USBD_SHORT_ TRANSFER_OK flag bit if a short input transfer is acceptable.

The ReservedBits field is usually zero, but if it is a value between 4 and 31 this value is used for bits 4 to 0 in the SETUP packet bmRequestType field (see Table 20.3 in the last chapter).

The Request, Value, Index, and TransferBuffer fields are set appropriately for the vendor– or class-defined request.

URB Isochronous Frame Functions

Request Control of Frame Length

Function codes URB_FUNCTION_TAKE_FRAME_LENGTH_CONTROL URB_FUNCTION_RELEASE_FRAME_LENGTH_CONTROL

Build routine none available

URB Structure _URB_FRAME_LENGTH_CONTROL

Use this function to request control of the frame length (i.e., SOF mastership).

Only one client can have control of frame length at any time.

Get Current Frame Length

Function codes URB_FUNCTION_GET_FRAME_LENGTH

Build routine none available

URB Structure _URB_GET_FRAME_LENGTH

Use this function to get the current frame length in the FrameLength field. It also indicates in FrameNumber the first frame in which the frame length can be changed.

Set Current Frame Length

Function codes URB_FUNCTION_SET_FRAME_LENGTH

Build routine none available

URB Structure _URB_SET_FRAME_LENGTH

Use this function to specify the amount to change the current frame length (i.e., 1 or –1 in the FrameLengthDelta field).

You must have control of frame length to issue this URB.

Get Current Frame Number

Function codes URB_FUNCTION_GET_CURRENT_FRAME_NUMBER

Build routine none available

URB Structure _URB_GET_CURRENT_FRAME_NUMBER

Use this function to get the current frame number in FrameNumber.

Gets Beginning Frame of Pattern Transfer

Function codes URB_FUNCTION_SYNC_FRAME

Build routine none available

URB Structure _URB_CONTROL_SYNC_FRAME_REQUEST

This function retrieves the beginning frame of a pattern transfer from an isochronous pipe.

This function is documented, but both the function code and the URB structure are not defined in the W98 and W2000 DDK USB headers.

Conclusion

The chapter has looked in detail at the Windows USB Device Interface (USBDI) and shown how to use it to talk to a USB HID keyboard. The USB class drivers make several Internal IOCTLs available to USB client device drivers. The most important of these lets you issue USB Request Blocks (URBs). Use URBs to do most interactions with your USB device.

The next chapter looks at the core theory of Human Input Devices (HID). The following chapter shows how to use a HID device in a client device driver and Win32 application.


  1. Presumably to avoid unnecessary copying of large buffers.

  2. Unlike a processor execution stack, there is nothing wrong with altering the next item down the stack.

  3. The UsbGetIdleRate routine shows how to read the idle rate. 

  4.  Note that the call to UsbBuildInterruptOrBulkTransferRequest must be in the loop for it to work in W2000. 

  5. Using KeQuerySystemTime is less efficient.