Sunday, August 31, 2014

Writing a WinUSB Driver (2 of ?): Fixed: WinUsb_GetAssociatedInterface fails with ERROR_NO_MORE_ITEMS when you try to open an alternate interface

This week I'm working on writing my first USB driver with WinUSB.  It's going pretty well.

I have hit two snags.  The first has to do with connecting to alternative interfaces on the device.  The second snag has to do with starting isochronous transfers.  That's a future post  (after I figure it out).

Some explanation is in order.

When you plug in a USB Device it is identified to the system by a Hardware ID.  You can see the Hardware ID in Device Manager.

Hardware Ids for the Intel Play Qx3 Microscope
There are two unique concepts for USB devices called "interfaces" and "endpoints".  We'll cover interfaces first.  Interfaces are device addresses, sort of like a computer's IP address on a network.  .  A device can have multiple "alternative" interfaces, the same way that a computer can have multiple IP Addresses.  (This will be important in a minute.)

Interfaces are only half the story.  The other half is "Endpoints".  These are analogous to "ports" in the networking world.  All devices are required to support a specific interface/endpoint called the "default".  What makes USB really fantastic is that you can ask the device for information about its other interfaces and endpoints.

One tool to query information is  USBView.  It's available free from Microsoft.  You can download it as part of the Windows SDK.  It installs to c:\Program Files (x86)\Windows Kits\8.1\Debuggers\x64\usbview.exe, in case you can't find it.  Microsoft gives away the source code for this app too, if you are interested.  Here's what it looks like.

I know this is dragging on, but I'm coming to a point.  The key thing is that a single USB device can have multiple interfaces and addresses.  The documentation for my USB device says:
[After connecting the device and selecting high-power mode] For USB cameras the driver, must now select Interface #1, with the highest possible alternate setting.
The alternate settings are numbered 1-3.  Each interface has slightly different speed parameters, and it's in my best interest to pick the fastest one.  Here is a trimmed snippet from USB view.  In this you can see that the packetsize is larger on the higher alternate numbers.

          ===>Interface Descriptor<===
bInterfaceNumber:                  0x00
bAlternateSetting:                 0x01
iInterface:                        0x00
wMaxPacketSize:                  0x01C0 = 0x1C0 bytes
          ===>Interface Descriptor<===
bInterfaceNumber:                  0x00
bAlternateSetting:                 0x03
iInterface:                        0x00
wMaxPacketSize:                  0x03C0 = 0x3C0 bytes

The WinUSB documentation says the right way to accomplish is to discover all of the interfaces with WinUsb_QueryInterfaceSettings and then connect to the new interface by calling the WinUsb_GetAssociatedInterface function.  Here's my code.

if (!(WinUsb_GetAssociatedInterface(oldWinusbHandle, alternateSettingNumber, &newWinusbHandle))) { printf("WinUSB_GetAssociatedInterface failed with error code %l.", GetLastError()); return (false); //This doesn't work. :( }

Unfortunately this fails with the error code 259 "ERROR_NO_MORE_ITEMS".

This is a showstopper for me because the default interface doesn't support the endpoint required to transfer images from the camera.  Argh!

I quintuple checked all of my code against the MSDN examples before I figured it out.  What I finally realized is that WinUsb_GetAssociatedInterface opens an entirely new connection to the alternative interface.  I don't want that, and my device doesn't support more than one connection at a time.  What I want to do is change the Alternative Setting on my existing connection.

The so-simple-it-hurts solution is to call WinUsb_SetCurrentAlternateSetting instead.

// Switch to the new best alternateSetting (This works!) if (!(WinUsb_SetCurrentAlternateSetting(deviceData.WinusbHandle, deviceData.bAlternateSetting))) { printf("WinUSB_SetCurrentAlternateSetting failed with error code %l", GetLastError()); return (false); }


On a related note, after you switch this, you also have to remember to specify the new alternate setting number when you get the list of endpoints:

WinUsb_QueryInterfaceSettings(deviceData.WinusbHandle, alternateSetting, &usbInterface);

and also when you get the pipe handle for the endpoints:

WinUsb_QueryPipeEx(deviceData.WinusbHandle, alternateSetting, i, &pipe);

That's it!

For those of you following this project, it's humming along nicely.  I have working control functions written for all of the camera functions that I'll need, plus a bunch I probably won't use.  I've also written a widget to convert the images from YUYV format to RGB so I can display them.  Where I'm stuck now is that all my USB isochronous transfers fail with an error 57: ERROR_ADAP_HDW_ERR.  Without those transfers I can't get any picture data from the camera.  That's a show stopper.  Once that's fixed I can start saving the pictures to disk and the "driver" part is done.  The grand finale will be a desktop app that can view the pictures in real time with buttons for the illumination and knobs for brightness, contrast, etc.  Those are all topics for another post.  Wish me luck!

No comments: