Windows System Software -- Consulting, Training, Development -- Engineering Excellent, Every Time.

Using WinUSB for User-Mode to USB Device Communication

Suppose you have a custom USB device that’s relatively simple.  Maybe the device has a pair of bulk endpoints on it, and perhaps also an interrupt in endpoint.  To make your device useful, all you really need to be able to do is allow a single application to read and write from those bulk endpoints and read from the interrupt endpoint.  Performance isn’t super-critical.  You don’t need to do anything too very fancy, and you’d rather not be bothered with writing a custom driver for your device.  If that’s the case, WinUSB might be for you.

WinUSB is a kernel mode driver that was developed concurrently with the Windows Driver Foundation (WDF).   This driver is available on Windows XP and later versions of Windows and has a user mode API, exported by WinUsb.dll, which allows applications to talk to USB devices.   In some cases a USB device developer who provides a single application to use their USB device may consider installing WinUSB as their device’s function driver instead of implementing a custom device driver.

This article talks in detail about WinUSB and demonstrates how to communicate with the OSR-FX2 Learning Kit device via WinUSB.    The code described in this article is downloadable from http://insider.osr.com/2009/code/winusb.zip and is easily extendable to other USB devices.

By the way, the readers of this article are expected to already understand USB concepts.  If you don’t, we suggest reading “USB in a NutShell” available at http://www.beyondlogic.org/usbnutshell.

WinUSB

As previously described, WinUSB is composed of a kernel mode driver and a user mode API exported via WinUsb.Dll.   This WinUSB API makes it easy for a user mode application to configure and access endpoints exported by a USB device.   WinUSB can be used by a developer to verify the integrity of a device before developing a device driver for the device, or as previously mentioned, can eliminate the need for a custom device driver in some cases.

Before you decided to use WinUSB for your device, you need to be aware of its features and limitations it supports.   A few of the most important of these are:

  • Supports bulk, interrupt, and control transfers
  • Supports selective suspend and wait/wake
  • Does not support multiple concurrent applications
  • Does not isolate driver address space from application address space
  • Does not support isochronous transfers
  • Does not support the installation of a filter

Also, there are certain classes of driver that require a custom driver, whether it be a UMDF or KMDF driver.  These include music players, dongles, network adapters and printer drivers.  But, other than these specific devices, if the limitations aren’t a problem WinUSB might be the answer.

Installing WinUSB for Your Device

Before we get started talking about using the WinUSB interface, we need to talk about installing the WinUSB drivers.   To do this you need to have an INF file that knows about your device, knows how to load the WinUSB software, has the correct information that ties WinUSB to your device, and finally defines the interface GUID that your user mode program is going to use to communicate with your device via WinUSB.   In addition, you are going to need the Microsoft redistributable binaries to install WinUSB and its associated software.   This sounds like a lot of work, but luckily Microsoft provided us with a template INF file that we can use and modify for our purposes.  Figure 1 shows the Microsoft template WinUSB installation file that we modified for our testing.

;
;   Microsoft WinUsb installation Inf File.
;   Modified by OSR to support the OSRFX2 Learning
;   kit device.
;
[Version]
Signature = "$Windows NT$"
Class = OsrWinUsbDeviceClass
ClassGuid={1D165DB4-107F-4547-AA0C-1736A55F240B}
Provider = %ProviderName%
;CatalogFile=OsrCatFile.cat

;
; Since our device is not a standard USB device, we
; need to define a new class for the device.
;
[ClassInstall32]
Addreg=OsrWinUsbClassReg

[OsrWinUsbClassReg]
HKR,,,0,%ClassName%
HKR,,Icon,,-1

 [Manufacturer]
%ProviderName% = OsrWinUsb_WinUSB,NTx86,NTamd64,NTia64

 [OsrWinUsb_WinUSB.NTx86]
%USB\OsrWinUsb.DeviceDesc% =USB_Install, USB\VID_0547&PID_1002


[USB_Install]
Include=winusb.inf
Needs=WINUSB.NT

[USB_Install.Services]
Include=winusb.inf
AddService=WinUSB,0x00000002,WinUSB_ServiceInstall

[WinUSB_ServiceInstall]
DisplayName     = %WinUSB_SvcDesc%
ServiceType     = 1
StartType       = 3
ErrorControl    = 1
ServiceBinary   = %12%\WinUSB.sys

[USB_Install.Wdf]
KmdfService=WINUSB, WinUsb_Install
[WinUSB_Install]
KmdfLibraryVersion=1.7

[USB_Install.HW]
AddReg=Dev_AddReg

[Dev_AddReg]
HKR,,DeviceInterfaceGUIDs,0x10000,"{b35924d6-3e16-4a9e-9782-5524a4b79bac}"


[Strings]
ProviderName="Osr Fx2 WinUsbTest"
USB\OsrWinUsb.DeviceDesc="Test using WinUSB only"
WinUSB_SvcDesc="WinUSB Test"
DISK_NAME="Osr Install Disk"
ClassName="OsrWinUsbClass"

Please note that we removed some sections for readability purposes, the INF file supplied with the code associated with this article is complete

Since this is not an article on creating INF files, we won’t spend a lot of words on describing all the sections and their purposes.   What we will do, however, is point out the sections that you will want to modify for your own purposes.

  • [Version] – In this section you are going to want to create your own unique class name and class GUID so that you don’t conflict with any other WinUSB user. If you do this, don’t forget to modify the ClassName value in the [Strings] section.
  • [Manufacturer] – In this section you need to update the USB hardware Device and Vendor IDs to match the device that you want WinUSB to support. In our case the OSR USB-FX-2 LK board identifier is USB\VID_0547&PID_1002. Don’t forget to update the ProviderName value in the [Strings] section.
  • [Dev_AddReg] – In this section, you need to replace the supplied GUID with one that you generate. This GUID is important, because it is the GUID that your application will supply to the SetupDixxx routines when looking for devices that your application uses.  So in our INF file, the GUID specified is {b35924d6-3e16-4a9e-9782-5524a4b79bac}, and in the application we specify the matching GUID as shown in Figure 2.
  • [Strings] – In this section, you are going to want to modify the supplied strings so that they are descriptive or unique enough for your purposes.
//
// This GUID must match the interface GUID that is specified in the OSRWINUSB.INF File.
//
//{b35924d6-3e16-4a9e-9782-5524a4b79bac}
DEFINE_GUID(GUID_DEVINTERFACE_OSRUSBFX2,0xb35924d6, 0x3e16, 0x4a9e, 0x97, 0x82, 0x55, 0x24, 0xa4, 0xb7, 0x9b, 0xac);

Once you have performed your INF file modifications, the next thing to do is to install your device.   If there are not already drivers available for your device (see “Installation Caveat”, sidebar below) the Hardware Manager should ask you to supply software for the detected device.   All you should have to do is point it to the directory tree containing your INF file and the Microsoft redistributable binaries, WinUSBCoInstaller.dll, WdfCoInstaller01007.dll, and WUDFUpdate_01007.dll that we mentioned earlier and everything should be ready.

Installation Caveat
When we installed the OSR USB-FX2 device on our clean Vista Ultimate system, the Hardware Manager determined that our device was supported by another manufacturer’s driver. It downloaded and installed the driver for it. If this happens to you, you are going to have to uninstall that driver (and hopefully delete it at the same time), then using Device Manager, perform an update such that you can specify your software to be installed for the device.

Connecting to Your Device

Now that we have the software installed, the next thing we have to do is to find our device so that we can use WinUSB to communicate with it.   So the question is, how do we do that?   Well, as we mentioned above, our INF file specifies an Interface GUID ([Dev_AddReg] section) that WinUSB will register on our device’s behalf when our device is detected.  So our application program needs to enumerate all the devices that are loaded and have registered as supporting the Interface GUID that matches our INF file.

This enumeration is done using the Windows SetupDIxxx API.   Using this API, an application program can enumerate all devices that support a supplied GUID.   To do this, the application must perform the following operations:

  1. Call SetupDiGetClassDevs supplying the Interface GUID of interest and specifying DIGCF_PRESENT | DIGCF_ DEVICEINTERFACE as flags so that the call knows that we are only interested in enumerating device interfaces for devices that are currently present in the system.
  2. Call SetupDiEnumerateDeviceInterfaces in order to retrieve information about a device that matches the search criteria specified in our previous call to SetupDiGetClassDevs. Since there could be more than one device that matches our search, this function may be called iteratively until it returns ERROR_NO_MORE_ITEMS to indicate that the search is done.
  3. Upon a successful return from the call to SetupDiEnumerateDeviceInterfaces, we must call SetupDiGetDeviceInterfaceDetail in order to get details about the found device interface. The information returned from this call contains the “DevicePath“.  DevicePath contains the device interface path, and this field can be passed to the Windows CreateFile API to open a handle to your device.
  4. Once you’ve completed the enumeration of all devices supporting your Interface GUID , don’t forget to call SetupDiDestroyInfoList to release all the memory that was used while performing the iterations.

Now that we’ve found a device or devices that support our Interface GUID, and performed a CreateFile to get a handle to the device the next thing we have to do is associate our device with WinUSB.

Associating a Device with WinUSB

In order to communicate with our device using the WinUSB DLL, we must get a WinUSB interface handle which will associate our device with WinUSB.   This is performed via the call WinUsb_Initialize.  The call takes as input the HANDLE we got when we called CreateFile, using the DevicePath returned from our call to SetupDiGetDeviceInterfaceDetail, and returns to us a WINUSB_INTERFACE_HANDLE that we will use on all our subsequent calls to the WinUSB DLL APIs.

The WinUSB API

So how does an application actually talk to a device via WinUSB?  The WinUSB API is a rich interface which provides your application with the calls necessary to control most simple devices. The API is shown below:

WinUSB API API Description
WinUSB_AbortPipe Aborts all pending transfers for a pipe
WinUSB_ControlTransfer Transmits control data over a default control endpoint
WinUSB_FlushPipe Discards any data that is cached in a pipe
WinUSB_Free Frees all resources allocated by WinUSB_Initialize
WinUSB_GetAssociatedInterface Retrieves a handle for an associated interface
WinUSB_GetCurrentAlternateSetting Gets the current alternate interface setting for an interface
WinUSB_GetDescriptor Returns a requested descriptor
WinUSB_GetOverlappedResult Retrieves the results of an overlapped operation
WinUSB_GetPipePolicy Retrieves the policy for a specific pipe
WinUSB_GetPowerPolicy Retrieves the power policy for a device
WinUSB_Initialize Retrieves a handle for the WinUSB interface that is associated with the device.
WinUSB_QueryDeviceInformation Returns information about the device
WinUSB_QueryInterfaceSettings Returns the interface descriptor for the specified  interface
WinUSB_QueryPipe Returns information about a specified pipe for a specified interface
WinUSB_ReadPipe Reads data from a specified pipe
WinUSB_ResetPipe Resets the data toggle and clears the stall condition on a pipe
WinUSB_SetCurrentAlternateSetting Sets the alternate setting of an interface
WinUSB_SetPipePolicy Sets the policy for a specified pipe
WinUSB_SetPowerPolicy Sets the power policy for a device
WinUSB_WritePipe Writes data to a specified pipe

As you can see, the APIs provide your application with pretty much all the same functionality you would get if you implemented a UMDF/KMDF or WDM USB driver.  Great API!  So how do we use it?

Using the WinUSB API

As we mentioned in the introduction, we are assuming that the readers of the article are familiar with USB devices and how to use them on Windows.   Given that, let’s talk about using the API.   In order not to repeat information we already talked about in the sections  “Connecting to your device” and “Associating a device with WinUSB” we are going to assume that we’re already connected to our device with CreateFile and associated our device with WinUSB by calling WinUSB_Initialize.

Once WinUSB_Initialize has completed successfully, we are ready to start using our device.   Unfortunately, we really don’t know how to do that yet.   While we can issue transmit and receive data over the default control endpoint using WinUSB_ControlTransfer, we really cannot use the full potential of our device until we find a USB Interface Descriptor that our device defines that contains the interface want to use (remember a device can support multiple interfaces, so we may have to enumerate all the supported interfaces).   In addition, once we find that interface, we must then enumerate the pipes supported by that interface such that we know what pipe types are available for use.   Luckily, this is all easy to do with the WinUSB API.   To enumerate the interfaces supported by your device, call WinUSB_QueryInterfaceSettings.   Once you’ve enumerated the interfaces and found the interface that supports the features you want to use, call WinUSB_QueryPipe in order to enumerate the pipes supported by the interface you’ve elected to use.

Now if that isn’t simple for determining your configuration, then we don’t know what simple is.

Okay, so we’ve now picked an interface and received information on the pipes supported by the interface.  Our next big step is to perform a data transfer.   Okay, this is where it gets complicated. NOT!   Want to read data from an input pipe? Call WinUSB_ReadPipe.  Want to write data to an output pipe? Call WinUSB_WritePipe. Want to use overlapped I/O when you do your operations?   Well, these APIs support that too, so you can perform asynchronous I/O with WinUSB.

So, that’s all there is to using the WinUSB API.   Find your device, connect to it, enumerate the interfaces supported, select one, enumerate the pipes supported by the interface selected, and then you are off to the races.

Guess it’s about time to start talking about the sample code (WINUSB) we wrote to illustrate use of the WinUSB API to talk to the OSR USB FX2 Learning Kit device.

WINUSB Sample Code

In order to illustrate how easy it is to use WinUSB and the WinUSB API we decided to implement all the functionality provided by the full-fledged, WDM-based OSR USB-FX2 Example driver, located on OSRONLINE.   In addition, we created a C++ class (CWinUsbDevice) to encapsulate a lot of the functions that all WinUSB applications need in order to allow you to get a jump on your development (This class is in no means complete, so if you want to expand it, enhance it, please do so and send us the updates to that we can make sure that everyone benefits!).

Anyway, the functionality provided by the OSR USB-FX2 Learning Kit and its associated test program are:

  • Light Bar Graph LEDs individually
  • Light all Bar Graph LEDS
  • Clear all Bar Graph LEDS
  • Get Bar Graph LED state
  • Get Switch Pack State
  • Get Switch Pack Interrupt message on Switch Change
  • Get 7 Segment Display State
  • Cycle 7 Segment Display Lights
  • Cycle Bar Graph LEDS
  • Do Bulk I/O

The WINUSB sample code that we’re providing contains two CPP files, WinUsbApp.cpp and WinUsbDevice.cpp.   WinUsbApp is a Windows console mode program which implements main and all routines necessary to format commands to implement the functionality that the OSR USB-FX2 supports.   WinUsbDevice implements the CWinUsbDevice class which encapsulates all the common functionality that a user of the WinUSB API would have to implement.  The CWinUsbDevice class is defined Figure 3.

class CWinUsbDevice : public CObject
{
public:
	CWinUsbDevice(LPGUID Guid);
	virtual ~CWinUsbDevice();

	DWORD OpenUsbDevice(int MemberIndex=0);
	const CStringArray& GetDevices() { return m_DevicePath; };
	DWORD GetUsbDevices(int& Count);
	DWORD QueryUsbDeviceInformation(ULONG InformationType,
					PVOID Buffer,PULONG BufferLength);

	DWORD QueryUsbInterfaceSettings(int Limit,int& Count);
	const CArray<USB_INTERFACE_DESCRIPTOR,USB_INTERFACE_DESCRIPTOR>& 
         GetUsbDeviceInterfaces(){
		return m_UsbInterfaceDescriptors;
	};

	DWORD QueryUsbPipeInformation(int Interface,int& Count);
	const CArray<WINUSB_PIPE_INFORMATION,WINUSB_PIPE_INFORMATION>& 
               GetUsbDevicePipeInformation() {
		return m_UsbPipeInformation;
	};

	DWORD WriteToDevice(UCHAR PipeId,PUCHAR Buffer,ULONG BytesToWrite,
				  ULONG& BytesWritten,LPOVERLAPPED POverlapped);
	DWORD ReadFromDevice(UCHAR PipeId,PUCHAR Buffer,ULONG BytesToRead,
				  ULONG& BytesRead,LPOVERLAPPED POverlapped);
	DWORD DoControlTransfer(WINUSB_SETUP_PACKET& Packet,PUCHAR Buffer,ULONG BufferSize, 
                 ULONG& BytesTransfered,LPOVERLAPPED POverlapped);

	DWORD FlushUsbPipe(UCHAR PipeId);
	DWORD AbortUsbPipe(UCHAR PipeId);
	DWORD ResetUsbPipe(UCHAR PipeId);

private:
	LPGUID			m_DeviceGuid;
	HANDLE			m_DeviceHandle;
	CStringArray	m_DevicePath;
	HANDLE			m_WinUsbHandle;
	BOOL			m_bInitialized;
	int				m_InterfaceNumber;

	CArray<USB_INTERFACE_DESCRIPTOR,USB_INTERFACE_DESCRIPTOR> m_UsbInterfaceDescriptors;
	CArray<WINUSB_PIPE_INFORMATION,WINUSB_PIPE_INFORMATION> m_UsbPipeInformation;

	DWORD GetDevicePath();
};

To use the class, a caller would do the following:

  1. Instantiate an instance by calling the constructor CWinUsbDevice(GUID) passing in the GUID of the interface that will be associated with this instance of the class.
  2. Call the member function GetUsbDevices(Count) which, upon successful completion, returns in Count the number of devices found that support the interface GUID that was specified when the class was instantiated.
  3. If the more that one device was found by GetUsbDevices, Call GetDevices to get a list of all the devices found.
  4. Once a device has been selected from the list, Call OpenUsbDevice passing in the index of an entry in the list returned by GetUsbDevices.
  5. If the call to OpenUsbDevice succeeds, CWinUsbDevice has called WinUSB_Initialize and we have now successfully associated our device with WinUSB.

As we mentioned, at the completion of step 5, we have now associated our device with WinUSB. However as we stated in the section “Using the WinUSB API” we’re not really ready to use the full potential of our device until we have selected the USB_INTERFACE_DESCRIPTOR to use and queried the pipes provided by that interface.  So to continue we must do the following:

  1. We must call QueryUsbInterfaceSettings(Limit,Count) in order to enumerate the interfaces supported by our USB device. This routine takes 2 parameters: Limit, which specifies the maximum number of interfaces we want to enumerate, and Count which upon successful return indicates the number of interfaces found.
  2. If more than one interface was found, Call GetUsbDeviceInterfaces to get a list of interfaces to select from. Once an interface has been selected you are ready to get information about the pipes supported by the selected interface.
  3. A list of all the pipes supported by a particular interface is generated by calling QueryUsbPipe Information(Interface,Pipe Count). This routine takes as input Interface the list index of the interfaces contained in the list retrieved by GetUsbDeviceInterfaces, and also PipeCount, which on successful return from QueryUsbPipeInformation contains the number of pipes found for the specified interface.
  4. A list of the pipes supported for Interface can be retrieved by calling GetUsbDevicePipe Information.

Believe it or not, at this point we have all the information necessary to begin performing operations on our device.   So for example, let’s assume that what we would like to do is cycle the OSR USB-FX2 device Bar Graph light.   To do that we would do the operations contained within the ControlTheLights function in WinUsbApp.   That function is shown in Figure 4.

//
// Control the lights on the fx2 board.   Toggle the lights
// in the LED Bar Graph for 10 iterations.
//
DWORD ControlTheLights(CWinUsbDevice& Device)
{
	WINUSB_SETUP_PACKET	packet;
	UCHAR				lightBar;
	ULONG				bytesReturned;
	DWORD				status = ERROR_SUCCESS;

	packet.RequestType = 0;
	packet.Request = USBFX2LK_SET_BARGRAPH_DISPLAY;   // Command to set the bar lights.
	packet.Index = 0;
	packet.Length = sizeof(lightBar);
	packet.Value = 0;

	for(ULONG index = 0; index < 10; index++) {
		for(ULONG light = 0; light < 8; light++) {
			lightBar = 1 << light;

			status = Device.DoControlTransfer(packet,&lightBar,sizeof(lightBar),
						bytesReturned,NULL);

			if(status != ERROR_SUCCESS) {
				return status;
			}
			Sleep(500);
		}
	}

	return status;
}

Now we want to issue a command to read the state of the switch pack on the device.  To do that we would perform all the operations contained within the ReadTheSwitches function in WinUsbApp. That function is shown in Figure 5.

//
// Read the OSR-FX2 Switch Pack switch settings.
//
DWORD ReadTheSwitches(CWinUsbDevice& Device)
{
	WINUSB_SETUP_PACKET	packet;
    SWITCH_STATE		switchState;
	ULONG				bytesReturned;
	DWORD				status = ERROR_SUCCESS;
	USB_DEVICE_REQUEST	request;

	//
	// Indicate to WinUSB that we are sending a vendor request
	// to the endpoint and there will be data returned to
	// the host.
	//
	request.Bits.Recipient = 2;  // Endpoint
	request.Bits.Type = 2; // Vendor
	request.Bits.DataDirection = 1; // Device to Host

	packet.RequestType = request.bmRequestType;
	packet.Request = USBFX2LK_READ_SWITCHES;   // Read the switches.
	packet.Index = 0;
	packet.Length = sizeof(switchState);
	packet.Value = 0;

    switchState.SwitchesAsUChar = 0;

	status = Device.DoControlTransfer(packet,(PUCHAR) &switchState,sizeof(switchState),
				bytesReturned,NULL);

	if(status != ERROR_SUCCESS) {
		return status;
	}

    _tprintf(_T("Switches: \n"));
    _tprintf(_T("    Switch8 is %s\n"), switchState.Switch8 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch7 is %s\n"), switchState.Switch7 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch6 is %s\n"), switchState.Switch6 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch5 is %s\n"), switchState.Switch5 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch4 is %s\n"), switchState.Switch4 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch3 is %s\n"), switchState.Switch3 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch2 is %s\n"), switchState.Switch2 ? _T("ON")  : _T( "OFF"));
    _tprintf(_T("    Switch1 is %s\n"), switchState.Switch1 ? _T("ON")  : _T( "OFF"));

	return status;
}

Finally, let’s look at how to transfer bulk data to and from your device.   Since the OSR USB-FX2 LK device firmware loops back whatever data has been written to the Bulk Input pipe, we have implemented a function in WinUsbApp that writes data to the Bulk Out Pipe and then reads the data back on the Bulk In Pipe.   This functionality is demonstrated in the Figure 6.

//
// Perform Bulk I/O on the bulk in and out pipes.
//
DWORD DoBulkIo(CWinUsbDevice& Device)
{
	UCHAR	inPipe = 0xFF;
	UCHAR	outPipe = 0xFF;
	USHORT  in_maximumPacketSize;
	USHORT  out_maximumPacketSize;
	DWORD	bytesWritten;
	DWORD	bytesRead;
	DWORD	status;
	DWORD	iterations = 0;

	const CArray<WINUSB_PIPE_INFORMATION,WINUSB_PIPE_INFORMATION>& 
		pipeList = Device.GetUsbDevicePipeInformation();


	for(int pipeIndex = 0; pipeIndex < pipeList.GetSize(); pipeIndex++) {
		if(inPipe == 0xFF && pipeList[pipeIndex].PipeType == UsbdPipeTypeBulk &&
			USB_ENDPOINT_DIRECTION_IN(pipeList[pipeIndex].PipeId)) {
			inPipe = pipeList[pipeIndex].PipeId;
			in_maximumPacketSize = pipeList[pipeIndex].MaximumPacketSize;
			_tprintf(_T("\tIn Pipe MaximumPacketSize: %d\n"),pipeList[pipeIndex].MaximumPacketSize);
		}
		if(outPipe == 0xFF && pipeList[pipeIndex].PipeType == UsbdPipeTypeBulk &&
			USB_ENDPOINT_DIRECTION_OUT(pipeList[pipeIndex].PipeId)) {
			outPipe = pipeList[pipeIndex].PipeId;
			out_maximumPacketSize = pipeList[pipeIndex].MaximumPacketSize;
			_tprintf(_T("\tOut MaximumPacketSize: %d\n"),pipeList[pipeIndex].MaximumPacketSize);
		}
	}

	if(inPipe == 0xFF || outPipe == 0xFF) {
		return ERROR_INVALID_PARAMETER;
	}

	Device.ResetUsbPipe(inPipe);
	Device.ResetUsbPipe(outPipe);
	Device.FlushUsbPipe(inPipe);
	Device.FlushUsbPipe(outPipe);

	PUCHAR pInBuffer = new UCHAR[min(out_maximumPacketSize,in_maximumPacketSize)];

	if(!pInBuffer) {
		return ERROR_NOT_ENOUGH_MEMORY;
	}

	DWORD  ioLength = min(out_maximumPacketSize,in_maximumPacketSize);

	_tprintf(_T("\tCalculated MaximumPacketSize: %d\n"),ioLength);

	PUCHAR pOutBuffer = new UCHAR[ioLength];

	if(!pOutBuffer) {
		delete []pInBuffer;
		return ERROR_NOT_ENOUGH_MEMORY;
	}
	_tprintf(_T("\n\tI/O Iterations [1-4294967295]: "));

	if (scanf ("%d", &iterations) <= 0) {
		_tprintf(_T("Error reading input!\n"));
		return 1;
	}

	for(ULONG index = 0; index < iterations; index++) {
		status = Device.WriteToDevice(outPipe,pOutBuffer,ioLength,bytesWritten,NULL);

		if(status != ERROR_SUCCESS) {
			_tprintf(_T("Error: Write interation %d status %d!\n"),iterations,status);
			break;
		}


		status = Device.ReadFromDevice(inPipe,pInBuffer,ioLength,bytesRead,NULL);

		if(status != ERROR_SUCCESS) {
			_tprintf(_T("Error: Read interation %d status %d!\n"),iterations,status);
			break;
		}

		if(bytesWritten != bytesRead) {
			_tprintf(_T("Error:Data Length issue iteration: %d status %d!\n"),iterations,status);
			status = ERROR_INVALID_BLOCK;
		}
	}

	delete []pInBuffer;
	delete []pOutBuffer;

	return status;
}

As you can see, performing Bulk I/O is rather simple.   Allocate a buffer, fill it in and call WriteToDevice (which calls WinUSB_WritePipe) to output data on a specified output pipe, then call ReadFromDevice (which calls WinUsb_ReadPipe) to read data from the specified input pipe.   It couldn’t be any easier…

Something to Be Aware Of

As mentioned in the introductory section, WinUSB supports wait/wake, so in our testing for this article we decided to try it out.   WinUSB has a WinUSB_SetPowerPolicy API that allows the caller to enable and disable wait wake.   Try as we might, we could never get this to API to work.  We always received the error ERROR_INVALID_PARAMETER from the call.   While we did get the device to support wait/wake by setting the “SystemWakeEnabled” key in the OSRWinUSB.INF file contained within the sample code, we just never could enable or disable it via the API.

Since we were perplexed about this problem we decided to submit a question to the NTDEV newsgroup.  One of the PMs at Microsoft confirmed that the documentation for WinUSB_SetPowerPolicy is indeed wrong, and this function doesn’t support wait/wake.  All you need to do is set the registry key (which will also result in a power policy tab appearing in Device Manager for your device).

Summary

WinUSB is a great tool for USB device developers.   For certain devices it may be possible to remove the need for a custom device driver (UMDF, KMDF, or WDM) all together.   At the very least it can be used by developers to validate the functionality of a device prior to implementing a device driver in order to verify the devices integrity.