52964.fb2
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.
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.
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_TIMEOUT | Input | ULONG Time-out in seconds | Specifies the time-out for subsequent read requests |
IOCTL_USBKBD_GET_DEVICE_DESCRIPTOR | Output | Received device descriptor | Read the device descriptor. |
IOCTL_USBKBD_GET_CONFIGURATION_DESCRIPTORS | Output | Received descriptors | Read the first configuration descriptor, along with any associated, interface, endpoint, class and vendor descriptors. |
IOCTL_USBKBD_GET_SPECIFIED_DESCRIPTOR | Input | ULONG Descriptor type, ULONG Descriptor size | Read the specified descriptor into the given sized buffer |
Output | Received descriptor | ||
IOCTL_USBKBD_GET_STATUSES | Output | 6-byte buffer for status values | Read the device, first interface and first endpoint status values (16 bits each) |
IOCTL_USBKBD_GET_FRAME_INFO | Output | 12-byte buffer for frame information | Read 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 |
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.
The USB header files that you might need to include are in the DDK.
usb100.h | Various USB constants and structures |
usbioctl.h | IOCTL definitions |
usbdlib.h | URB building and assorted routines |
usbdi.h | USBDI 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
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_URB | Submit URBBlock awaiting result |
IOCTL_INTERNAL_USB_RESET_PORT | Reset and reenable a port |
IOCTL_INTERNAL_USB_GET_PORT_STATUS | Get port status bits: USBD_PORT_ENABLED USBD_PORT_CONNECTED |
IOCTL_INTERNAL_USB_ENABLE_PORT | Reenable a disabled port |
IOCTL_INTERNAL_USB_GET_HUB_COUNT | Used internally by hub driver |
IOCTL_INTERNAL_USB_CYCLE_PORT | Simulates a device unplug and replug |
IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO | Used internally by hub driver |
IOCTL_INTERNAL_USB_GET_HUB_NAME | Get the device name of the USB hub |
IOCTL_INTERNAL_USB_GET_BUS_INFO | Fills in a USB_BUS_NOTIFICATION structure (W2000 only) |
IOCTL_INTERNAL_USB_GET_CONTROLLER_NAME | Get the host controller device name (W2000 only) |
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 bits | Interpretation | Macro |
---|---|---|
00 | Completed successfully | USBD_SUCCESS |
01 | Request is pending | USBD_PENDING |
10 | Error, endpoint not stalled | USBD_ERROR |
11 | Error, endpoint stalled | USBD_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.
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.
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;
}
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;
}
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;
}
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.
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.
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;
}
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;
}
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.
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
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.
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 bDeviceProtocol | The USB class codes |
idVendor idProduct bcdDevice | Vendor, product, and version numbers |
iManufacturer iProduct iSerialNumber | Indexes into the string descriptor for the relevant strings |
bMaxPacketSize0 | The maximum packet size for Endpoint 0 |
bNumConfigurations | The 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
wTotalLength | Total length of all data for the configuration |
bNumInterfaces | Number of interfaces |
iConfiguration | Configuration number |
bmAttributes | bit 5 set: supports remote wake up |
bit 6 set: self-powered | |
bit 7 set: powered from bus | |
maxPower | Maximum 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
bInterfaceNumber | Interface number |
bAlternateSetting | Alternate setting |
bNumEndpoints | Number of endpoints |
bInterfaceClass bInterfaceSubClass bInterfaceProtocol | USB class codes |
iInterface | Index 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
InterfaceDescriptor | Points to interface descriptor |
Interface | USB_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
InterfaceNumber | Interface number |
AlternateSetting | Alternate setting |
Class Subclass Protocol | USB class codes |
NumberOfPipes | Number of pipes |
PipeInformation | Array 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
MaximumPacketSize | Maximum packet size |
EndpointAddress | Endpoint number The top bit is set for input pipes. |
Interval | Polling interval in ms for Interrupt pipes |
PipeType | UsbdPipeTypeControl UsbdPipeTypeIsochronous UsbdPipeTypeBulk UsbdPipeTypeInterrupt |
PipeHandle | Handle for later USBDI calls |
MaximumTransferSize | Maximum transfer size |
PipeFlags | Flags |
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.
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.
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.
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.
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.
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.
Presumably to avoid unnecessary copying of large buffers.
Unlike a processor execution stack, there is nothing wrong with altering the next item down the stack.
The UsbGetIdleRate routine shows how to read the idle rate.
Note that the call to UsbBuildInterruptOrBulkTransferRequest must be in the loop for it to work in W2000.
Using KeQuerySystemTime is less efficient.