BULKUSB

[This is preliminary documentation and subject to change.]

SUMMARY

This document and the associated sample source code describe how to handle the transfer of large amounts of data to and from a USB peripheral using the USB Driver Interface (USBDI). This article shows driver writers of USB devices such as digital cameras, printers, and scanners much of what they need to know to create or improve their own Win32 Driver Model (WDM) USB minidrivers.

In addition to handling asynchronous bulk data transfers, the sample source code demonstrates the correct way to handle Plug and Play and power management I/O Request Packets (IRPs), which are the basic I/O Manager structures used to communicate with the minidriver. See the Microsoft Windows NT 5.0 DDK for background information on the IRPs discussed in this sample.

What is the USB Driver Interface?

USBDI is part of the WDM layered architecture for the Windows 98 and Windows NT 5.0 operating systems and is the interface offered to kernel-mode minidrivers by the operating system USB driver stack. A portion of the WDM layered architecture, with the USB driver interface highlighted, is shown in Figure 1.

Figure 1. USB Driver Interface and the USB Driver Stack

The following modules are shown in Figure 1:

A USB minidriver communicates with the USB stack through an IRP interface. There are two basic methods and both are used by Bulkusb.sys:

 

What is Bulk Data Transfer?

The USB standard defines four data transfer types: control, isochronous, interrupt, and bulk. All USB peripherals must support the control transfer type for configuration, command, and status information. Each of the remaining three data transfer types targets a particular category of USB peripheral. The bulk transfer type targets USB devices such as printers, scanners, and digital cameras that move large amounts of data to or from the PC over USB.

The USB device used for this sample is a generic Intel i82930 USB evaluation board programmed with a simple loopback test using a 64-KB circular buffer. None of the code in the BulkUSB sample is specific to this particular controller chip, except for some checked-build-only debug dumps indicating manufacturer-specific information from the USB_DEVICE_DESCRIPTOR obtained from the USB stack through USBDI at StartDevice() time. The URBs used to communicate with the USB stack use abstracted chip-independent device characteristics as defined in the URB request types and structures in Usbdi.h.

What Does the USB Minidriver Sample Do?

This sample consists of two parts: a USB minidriver (Bulkusb.sys) and a console application (Rwbulk.exe). They are described individually in this section. It is intended that driver writers apply the methods illustrated in this sample to their own devices. The sample code is heavily commented to improve its usefulness.

Bulkusb.sys USB Minidriver

Bulkusb.sys is a USB bulk I/O sample minidriver that transfers asynchronous data packets to and from an Intel i82930 USB peripheral microcontroller over USB. This minidriver also performs the power management and Plug and Play tasks that are required of any WDM USB minidriver. The particular device test board we used is part of the Intel USB developer’s kit. Information on obtaining the kit is available from Intel’s developer web site at http://developer.intel.com.

The individual source code files that make up the BulkUSB minidriver sample and their functions are the following:

Bulkusb.c

Contains DriverEntry and code that builds the dispatch table to functions that handle the IRPs and AddDevice request. URBs are sent to the USB driver stack from routines in Bulkusb.c.

BulkPwr.c

Contains code to handle power management IRPs.

BulkPnp.c

Contains code to handle Plug and Play IRPs.

IoctlBlk.c

Contains code to handle miscellaneous IRPs.

OcrwBlk.c

Contains code to handle basic I/O IRPs and code to perform asynchronous bulk data transfers.

BusbDbg.c

Contains the functions used for debug output.

The following header files are available in the same directory:

Blk82930.h

Defines DeviceExtension for instances of BulkUSB devices and the BULKUSB_RW_CONTEXT structure used for tracking information on staged IRP processing. Also defines the limits of this sample driver.

Bulkusb.h

Defines the Bulkusb.sys IOCTLs.

Guid829.h

Defines the globally unique identifier (GUID) used to generate symbolic links to driver instances created from user mode.

Rwbulk.exe Console Application

Rwbulk.exe is a console application used to initiate bulk transfer and to obtain a dump of information on the device’s I/O endpoints (named pipes) from USBDI. The application also demonstrates how to use GUID-based device names and pipe names generated by the operating system using the SetupDiXXX user-mode APIs. The source code file that makes up the Rwbulk console application sample and its function is the following:

RWBulk.c

Contains source code for a simple console application to test writing to a Bulk mode output pipe and reading from a Bulk mode input pipe. Various command-line switches allow for either single or multiple iterations of the read/write test with or without data dumps, as well as a dump of information on all the endpoints (named pipes) available on the device.

The following steps briefly describe the operation of the USB bulk I/O minidriver. The sample minidriver routines mentioned here are described in greater detail in the comments of the sample source code and in routine-specific comments later in this article. It is helpful here to follow along in the sample code.

Bulk I/O Operation: Loading

When Bulkusb.sys is loaded by the operating system, the minidriver’s DriverEntry routine (in Bulkusb.c) is called to set the Dispatch entry points for the MajorFunction IRPs that it handles and for AddDevice.

Notice that no resources are allocated here, because it should not be assumed that a Plug and Play device is actually plugged in at driver load time. Resource allocation and the creation of the Functional Device Object (FDO) do not occur until the device is detected by the USB stack. At this point, Plug and Play Manager calls the minidriver’s BulkUsb_PnPAddDevice routine (in Bulkpnp.c), which in turn calls the following functions:

BulkUsb_CreateDeviceObject (in Bulkusb.c) is called to create the FDO and a DeviceExtension. Notice that BulkUsb_SymbolicLink (in Bulkusb.c) is called here to create and initialize a GUID-based symbolic link to the device that is used to open/create instances of the minidriver from user mode.

BulkUsb_QueryCapabilities (in Bulkpwr.c) is called to learn which system power states are to be mapped to which device power states for honoring IRP_MJ_POWER IRPs. To do this, an IRP is created by Bulkusb.sys using IoAllocateIrp() and set up with MajorFunction = IRP_MJ_PNP and MinorFunction = IRP_MN_QUERY_CAPABILITIES. This IRP is sent to the PDO, which is represented by the USB class driver, using IoCallDriver(). A DEVICE_CAPABILITIES structure describing the device’s power capabilities is returned and saved in the FDO’s device extension.

BulkUsb_SelfSuspendOrActivate (in Bulkpwr.c) is called to try to power down the device until data transfer is actually requested. This is because there may be a significant time between device detection and the first time an application actually uses the device for I/O—for example, if a USB camera is kept plugged in at all times but used only occasionally. Bulkusb.sys attempts to power down the device when no I/O pipes are actually open on it.

After the USB stack assigns any resources to the device, it sends an IRP_MN_START_DEVICE that is handled by this minidriver’s BulkUsb_ProcessPnPIrp routine (in Bulkpnp.c). Several additional minor Plug and Play IRPs are used by Plug and Play Manager to ensure the orderly stopping and removal of devices for purposes such as resource balancing. See the "Stopping and Removing the Device" section later in this article for a discussion of how these minor Plug and Play IRPs are handled by BulkUsb_ProcessPnPIrp.

Bulk I/O Operation: Opening the Device

At this point the minidriver is operational; let’s talk about what needs to be done to start bulk data transfer. Rwbulk.exe initiates data transfer by making CreateFile, ReadFile, and WriteFile calls from within its main function. RWBulk.exe closes pipes with the CloseHandle() function. The handler functions for these calls are discussed next.

For an application to talk to a device, the application must obtain a handle to the device using the CreateFile call. When the system receives the CreateFile call, it sends an IRP_MJ_CREATE to the device. In the BulkUSB sample, IRP_MJ_CREATE IRPs are routed to the BulkUsb_Create function.

Note there is a fair amount of complexity to build up the file name used in the CreateFile call. To understand what happens here, we need to look back to BulkUsb_PnpAddDevice. When the device is added, BulkUsb_SymbolicLink (in Bulkusb.c) registers a symbolic link with the operating system. The symbolic link is based on a GUID defined in Guid829.h, generated by the Guidgen.exe utility, which is a sample utility provided in the Microsoft Platform. Software Development Kit (SDK).

Notice that driver writers should use Microsoft-defined GUIDs that correspond to their type of device, whenever available. If no applicable GUIDs can be found in the DDK header files, then generate a new GUID using Guidgen.exe.

Sometime after BulkUsb_PnPAddDevice is called, the Plug and Play Manager will send the BulkUSB driver an IRP_MN_PNP_START_DEVICE IRP. After the lower level drivers process this IRP, BulkUsb_ProcessPnPIrp calls BulkUsb_StartDevice. BulkUsb_PnpStartDevice then calls BulkUsb_ConfigureDevice that later calls BulkUsb_SelectInterface. Together these routines get and parse the BulkUSB device interface to allow the interface to be accessed from user-mode applications using the CreateFile API.

For brevity reasons, this sample only supports one interface; however, the sample can be extended to support multiple interfaces. A sensor glove is an example of a device where multiple interfaces might be desirable. The driver might create a simple joystick interface so the glove can be used with existing applications. The driver might also create a specialized glove interface that allows new applications to access the advanced capabilities of a glove sensor not available on a typical joystick interface.

Our particular test board was programmed to support six pipes, where:

Pipe #0 is a BULK-type INPUT pipe,

Pipe #1 is a BULK-type OUTPUT pipe,

Pipe #2 is a INTERRUPT-DRIVEN-type INPUT pipe,

Pipe #3 is a INTERRUPT-DRIVEN-type OUTPUT pipe,

Pipe #4 is a ISOCHRONOUS-type INPUT pipe, and

Pipe #5 is a ISOCHRONOUS-type OUTPUT pipe

User-mode applications ask the operating system to generate a unique device instance name based on the GUID used by the driver in the IoRegisterDeviceInterface routine by calling a sequence of SetupDi functions. The user-mode application then appends the pipe name to the device instance name and passes this concatenated string to CreateFile. Pipe names have the form PIPExx, where xx is an ASCII representation of an integer from 0 to the number of pipes supported by the USB device, minus one.

In this sample, it is assumed that the user-mode application knows that pipe #0 is the device’s input bulk pipe, and pipe #1 is the device’s output bulk pipe. Assuming that the device instance name returned from the SetupDiXXX calls is the following:

\\.\0000000000000086#{a1155b78-a32c-11d1-9aed-00a0c98ba608}

Then the user application would call CreateFile() on the following for input:

\\.\0000000000000086#{a1155b78-a32c-11d1-9aed-00a0c98ba608}\PIPE00

The application would call on the following for output:

\\.\0000000000000086#{a1155b78-a32c-11d1-9aed-00a0c98ba608}\PIPE01

The BulkUsb_Create function (which is called in response to the user-mode CreateFile API) parses the ASCII digit back out of the supplied pipe name and associates the pipe with its Nth endpoint. BulkUSB then essentially just sets a flag in its saved pipe information in its FDO device extension to indicate that the Nth pipe is open. No further hardware protocol is required at this point; no further URBs are created and sent to the device through USBDI until a read or write is actually performed.

BulkUsb_Close (in Ocrwblk.c) is the IRP-handler function for IRP_MJ_CLOSE and is the entry-point for CloseHandle calls from Rwbulk. BulkUsb_Close closes a pipe. Notice that when the last pipe is closed, BulkUsb_SelfSuspendOrActivate is called to power down.

Bulk I/O Operations: Data Transfer

BulkUsb_Read and BulkUsb_Write (both in Ocrwblk.c), which are the respective IRP-handler functions for IRP_MJ_READ and IRP_MJ_WRITE, are the entry points for ReadFile and WriteFile calls from user-mode code. These two functions immediately delegate their work to a common read/write function BulkUsb_StagedReadWrite (in Ocrwblk.c) because the difference between read logic and write logic is trivial.

BulkUsb_StagedReadWrite breaks up a read or write request into fixed-size pieces so the request can be completed in stages. This sample arbitrarily picks a size of 4096. The essential idea is that by breaking up a large user-mode I/O request into multiple IRPs in kernel mode, operating system throughput is maximized and single-process monopolizing of system resources is minimized by allowing other processes to do I/O between staged IRPs. The following pseudo code describes the process:



Calculate totalIrpsNeeded, the number of IRPs needed to break request into 4096-byte pieces.
Allocate space for array of totalIrpsNeeded BULKUSB_RW_CONTEXT structs.
For each IRP to be staged:
    Allocate the IRP with IoAllocateIrp
    Get the virtual address for the buffer described by our original
        input IRP's MDL, using MmGetMdlVirtualAddress;
    Each new IRP will 'see' the entire buffer, but map its I/O location to a
        single 4096 byte section within it using IoAllocateMdl and IoBuildPartialMdl.
    Call BulkUsb_BuildAsyncRequest to build and return an URB of type
        URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER;
 
    if (URB returned) then
        Call IoMarkPending() on the IRP, since it will not be completed until all the 
            new IRP’s we stage are completed.
        Call IoGetNextIrpStackLocation on the IRP to get nextStack, which is a pointer
    to the next-lower driver in the USB stack;

    nextStack->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL;
    nextStack->Parameters.Others.Argument1 = pointer to the returned URB;
    nextStack->Parameters.DeviceIoControl.IoControlCode =
        IOCTL_INTERNAL_USB_SUBMIT_URB;
    Call IoSetCompletionRoutine on the IRP and register 
       BulkUsb_AsyncReadWrite_Complete as the I/O completion routine.
       BulkUsb_AsyncReadWrite_Complete moves information from a completed
       asynchronous transfer URB to an IRP, resets the irpStackLocation, and
       deallocates the URB.
    Call IoCallDriver on the IRP to pass it to the USB stack;
    endif
endfor

Bulkusb.sys is a relatively simple minidriver that sends asynchronous bulk data transfer requests to the USB stack and uses only a handful of the possible requests. The following describes the requests used in this sample:

 

Bulk I/O Operation: Stopping and Removing the Device

Although there are a number of Plug and Play IRPs that various types of WDM drivers might have to handle, all USB minidrivers must handle the following minor Plug and Play device stopping and removal IRPs:

IRP_MN_CANCEL_REMOVE_DEVICE

IRP_MN_CANCEL_STOP_DEVICE

IRP_MN_QUERY_REMOVE_DEVICE

IRP_MN_QUERY_STOP_DEVICE

IRP_MN_REMOVE_DEVICE

IRP_MN_STOP_DEVICE

See the Windows NT 5.0 DDK for background information on the full list of IRPs.

The handlers for these in BulkUsb_ProcessPnPIrp (in Bulkpnp.c) are well commented, and you are strongly encouraged to look at the code. For all of the IRPs above, unless the driver fails the Plug and Play IRP, it must pass the IRP down the driver stack to the physical device object (PDO).

Note that no Plug and Play driver is allowed to fail IRP_MN_STOP_DEVICE or IRP_MN_REMOVE_DEVICE. The driver must be able to handle receiving either or both of these IRPs without having received their corresponding queries previously. An example of this is in the case of a power failure or if the user suddenly yanks the USB plug without warning.

  • IRP_MN_CANCEL_REMOVE_DEVICE is sent to indicate the device will not be removed; BulkUSB just resets its "remove-pending" DeviceExtension flag, indicating it can resume normal I/O.
  • IRP_MN_CANCEL_STOP_DEVICE is sent to indicate the device will not be reconfigured; BulkUSB just resets its "stop-pending" DeviceExtension flag, indicating it can resume normal I/O.
  • IRP_MN_QUERY_REMOVE_DEVICE is sent by the Plug and Play Manager to query whether a device can be physically removed without disrupting the system. It is sent just before IRP_MN_REMOVE_DEVICE during "polite" shutdowns, such as occur when the user explicitly requests the service be uninstalled or unplugged from Device Manager in Control Panel. A driver fails this IRP to inform the system that it is unsafe to remove the device at this time.

In this sample, the difference between the way BulkUSB handles the QUERY_STOP and the QUERY_REMOVE is that BulkUSB fails the "stop" query if any I/O is pending, whereas in the "remove" case, BulkUSB waits for any pending I/O to complete before returning SUCCESS to the query. IRP_MN_REMOVE_DEVICE will not be sent if the driver fails the IRP_MN_QUERY_REMOVE IRP. A driver that returns SUCCESS for the IRP should not accept any further I/O requests while it’s in the remove-pending state.

  • IRP_MN_QUERY_STOP_DEVICE is sent by the Plug and Play Manager to query whether a device can be stopped for resource reconfiguration. It is sent just before IRP_MN_STOP_DEVICE during "polite" reconfigurations, such as the user explicitly requesting reconfiguration from the Device Manager. Bulkusb.sys fails this IRP if any I/O is still pending. IRP_MN_STOP_DEVICE is only sent afterward if the query is successful. If the driver returns SUCCESS for the IRP, it must set a flag in the FDO DeviceExtension in order not to accept any further new I/O requests until reconfiguration is completed.
  • IRP_MN_REMOVE_DEVICE is sent when the device has been removed and probably physically detached from the computer. As with STOP_DEVICE, the driver cannot assume it has received any previous query and may have to explicitly cancel any pending I/O IRPs it has staged using IoCancelIrp(). Then, the GUID-based symbolic link registered at BulkUsb_PnpAddDevice is deleted by a call to IoSetDeviceInterfaceState(), and the FDO is detached from the USB stack by a call to IoDetachDevice() and deleted by a call to IoDeleteDevice().

IRP_MN_STOP_DEVICE is sent when the device is about to be reconfigured (not necessarily unplugged). Because the driver cannot assume it has received any previous query, it may have to explicitly cancel any pending I/O IRPs it has staged using IoCancelIrp(). BulkUsb_StopDevice essentially sends the USB stack a "select configuration" URB with a NULL pointer for the handle to close the configuration and put the device in the "unconfigured" state.

Power Management

All WDM drivers must handle the following IRPs in order to support power management:

IRP_MN_QUERY_POWER

IRP_MN_SET_POWER

IRP_MN_WAIT_WAKE

See the Windows NT 5.0 DDK for background information on the full list of IRPs.

The handlers for these in BulkUsb_ProcessPowerIrp (in Bulkpwr.c) are well-commented and you are strongly encouraged to look at the code. For each power management IRP, drivers must call PoStartNextPowerIrp and use PoCallDriver to pass the IRP all the way down the driver stack to the underlying PDO. The PoCallDriver interface is similar to that of IoCallDriver, except that the power management subsystem may delay the IRP before passing it on to the next driver—for example, if it must wait for sufficient power level to resume the PowerDeviceD0 (fully on) state.

In the case of setting the device power state, little needs to be done except to save the requested state in the FDO and pass the IRP down the stack to the PDO. In the "SystemPowerState" case, the basic objective is to set the device to the DevicePowerState most nearly equivalent to the SystemPowerState being set. This is done by again consulting the saved DEVICE_CAPABILITIES power-state table of the FDO and possibly generating a IRP_MN_SET_POWER IRP to change the device power state.

BulkUSB is a good example of active power management because it self-initiates power management requests in the function BulkUsb_SelfSuspendOrActivate (in Bulkpwr.c). In three cases, Bulkusb.sys initiates power IRPs:

 

Bulk I/O Operation: Handling IOCTLs

BulkUsb_ProcessIOCTL (in Ioctlblk.c) is the entry-point that is registered to handle IRP_MJ_DEVICE_CONTROL IRPs. Notice that there are circumstances under which BulkUsb_ProcessIOCTL can refuse to accept an I/O Control (IOCTL) request, such as if a device is removed. The IOCTLs handled by this function are described in this section and are defined in Bulkusb.h.

Bulkusb.h defines three IOCTLs for user-mode applications to access the driver using DeviceIoControl() calls, although this sample console application only uses one of them.

IOCTL_BULKUSB_GET_CONFIG_DESCRIPTOR calls BulkUsb_GetConfigDescriptor to get a copy of the USB configuration descriptor that Bulkusb.sys saves at AddDevice time, and then returns a copy of the descriptor in the user-mode I/O buffer. The configuration descriptor is parsed by Rwbulk.exe to provide a nicely formatted console dump of the device configuration and endpoints (named pipes).

The other two IOCTLs are not used by this sample console application, but may be used by a more sophisticated application to deal with certain user-mode device errors:

 

Supporting References for BulkUSB Sample Minidriver

The following additional materials will help you understand this sample minidriver:

© 1999 Microsoft Corporation