OSRLogo
OSRLogoOSRLogoOSRLogo x Seminar Ad
OSRLogo
x

Everything Windows Driver Development

x
x
x
GoToHomePage xLoginx
 
 

    Thu, 14 Mar 2019     118020 members

   Login
   Join


 
 
Contents
  Online Dump Analyzer
OSR Dev Blog
The NT Insider
The Basics
File Systems
Downloads
ListServer / Forum
  Express Links
  · The NT Insider Digital Edition - May-June 2016 Now Available!
  · Windows 8.1 Update: VS Express Now Supported
  · HCK Client install on Windows N versions
  · There's a WDFSTRING?
  · When CAN You Call WdfIoQueueP...ously

Multi-Interface Devices in KMDF

Not a WDF driver course goes by that we aren't asked by at least one student how to use a multi-interface device in KMDF. We always have to point them to a slide in the presentation that we think does the right thing, but without a multi-interface device to test we've been forced to be, shall we say, less than authoritative.

After many courses and unanswered prayers for students to stop asking a question we didn't really know the answer to, we decided to set forth and actually try to get a multi-interface device working. To do this, we hacked the firmware on the OSR USB FX2 learning kit to add a second interface with an isochronous endpoint. It wasn't pretty, but after a few sessions with our USB analyzer we actually got it to work.

EvtDevicePrepareHardware

Remember, when there are multiple interfaces in a USB device, each interface can be used in parallel and the endpoint addresses within each interface are unique within the configuration. As a result, there are only a few differences between a driver for a multi-interface device versus a single interface device. Almost all of the changes in the driver are likely to the contained within the EvtDevicePrepareHardware callback, where your driver selects a configuration and enumerates the various pipes on the device.

When you select your configuration, you need to supply a WDF_USB_DEVICE_SELECT_CONFIG_PARAMS structure. In the case of a multiple interface device, you will initialize this structure with WDF_USB_DEVICE_SELECT_ CONFIG_PARAMS_INIT_MULTIPLE_INTERFACE shown in Figure 1 and SettingPairs defined in Figure 2.

VOID
  WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(
    IN OUT PWDF_USB_DEVICE_SELECT_CONFIG_PARAMS  Params,
    IN OPTIONAL UCHAR  NumberInterfaces,
    IN OPTIONAL PWDF_USB_INTERFACE_SETTING_PAIR  SettingPairs
    );

Figure 1

typedef struct _WDF_USB_INTERFACE_SETTING_PAIR {
  WDFUSBINTERFACE  UsbInterface;
  UCHAR  SettingIndex;
} WDF_USB_INTERFACE_SETTING_PAIR, *PWDF_USB_INTERFACE_SETTING_PAIR;

Figure 2

The WDK documentation doesn't do such a great job with this routine and doesn't give much insight into why one may or may not want to supply the optional parameters. Here's what it says:

If the SettingPairs parameter is not NULL, this function sets the Type member to WdfUsbTargetDeviceSelect ConfigTypeInterfacesPairs. Otherwise, WDF_USB_DEVICE_SELECT_CONFIG _PARAMS_INIT_MULTIPLE_ INTERFACES sets the Type member to WdfUsbTargetDeviceSelectConfigType MultiInterface.

Inspecting the inline function shows that both NumberInterfaces and SettingPairs must be supplied together, otherwise SettingPairs will be ignored (See Figure 3).

VOID
FORCEINLINE
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(
    IN OUT PWDF_USB_DEVICE_SELECT_CONFIG_PARAMS Params,
    IN OPTIONAL UCHAR NumberInterfaces,
    IN OPTIONAL PWDF_USB_INTERFACE_SETTING_PAIR SettingPairs
    )
{
    RtlZeroMemory(Params, sizeof(WDF_USB_DEVICE_SELECT_CONFIG_PARAMS));

    Params->Size = sizeof(WDF_USB_DEVICE_SELECT_CONFIG_PARAMS);

    if (SettingPairs != NULL && NumberInterfaces != 0) {
        Params->Type = WdfUsbTargetDeviceSelectConfigTypeInterfacesPairs;

        Params->Types.MultiInterface.NumberInterfaces = NumberInterfaces;
        Params->Types.MultiInterface.Pairs = SettingPairs;
    }
    else {
        Params->Type = WdfUsbTargetDeviceSelectConfigTypeMultiInterface;
    }
}

Figure 3

So why would one want to use one version over the other? The answer lies in the SettingIndex member of the WDF_USB_INTERFACE_SETTING_PAIR structure. If you simply want to enable alternate setting zero on all of your interfaces, you can set the NumberInterfaces and SettingPairs parameters to NULL and KMDF will determine the number of interfaces you have an enable alternate setting zero on each. However, if you would like to enable an alternate setting on any of the interfaces you will need to create a WDF_USB_INTERFACE_SETTING_PAIR array and change the SettingIndex member appropriately.

For illustrative purposes, even though we're only enabling alternate setting zero on both of our interfaces we'll do so using a WDF_USB_INTERFACE_SETTING_PAIR array with the SettingIndex set to zero for each entry.

Configuring the WDF_USB_INTERFACE_SETTING_PAIR

Building the WDF_USB_INTERFACE_SETTING_PAIR structure is straightforward. We first dynamically determine the number of interfaces on the device and allocate a structure of the appropriate size (See Figure 4).

    //
    // Get the number of interfaces on our device.
    //
    numInterfaces =
            WdfUsbTargetDeviceGetNumInterfaces(devContext->BasicUsbUsbDevice);

    //
    // Allocate enough memory for the WDF_USB_INTERFACE_SETTING_PAIR
    // array.
    //
    settingPairsSize = (sizeof(WDF_USB_INTERFACE_SETTING_PAIR) * numInterfaces);

    settingPairs = (PWDF_USB_INTERFACE_SETTING_PAIR)
                                ExAllocatePoolWithTag(PagedPool,
                                                      settingPairsSize,
                                                      'bRSO');

    if(!settingPairs) {
#if DBG
        DbgPrint("Failed to allocate memory for Setting Pairs 0x%0x\n",
                        STATUS_INSUFFICIENT_RESOURCES);
#endif
        return STATUS_INSUFFICIENT_RESOURCES;
    }

Figure 4 - Building the WDF_USB_INTERFACE_SETTING_PAIR Structure (Part 1)

In our case we know that we only have two interfaces so we could have used a static array, but for a little more code we're able to handle any future changes to the firmware.

Then we simply loop, filling in the UsbInterface field of the structure by calling WdfUsbTargetDeviceGetInterface and filling in the SettingIndex field with the alternate setting of the interface we would like to enable. In our case we're just enabling alternate setting zero on each interface (Figure 5).

    // Iterate through the number of interfaces and initialize the WDF_USB_INTERFACE_SETTING_PAIR array.
    //
    for (interfaceIndex = 0; interfaceIndex < numInterfaces; interfaceIndex++) {
        //
        // Get the WDFUSBINTERFACE for this index.
        //
        settingPairs[interfaceIndex].UsbInterface =
                                    WdfUsbTargetDeviceGetInterface(
                                                devContext->BasicUsbUsbDevice,
                                                interfaceIndex);

        // We want alternate setting zero on all interfaces.
        //
        settingPairs[interfaceIndex].SettingIndex = 0;
    }

Figure 5 - Building the WDF_USB_INTERFACE_SETTING_PAIR Structure (Part 2)

Selecting the Configuration

From here, we're ready to initialize our WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT structure and select our configuration (Figure 6).

    // Init our WDF_USB_DEVICE_SELECT_CONFIG_PARAMS structure.
    //
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(
                                                &selectConfigParams,
                                                numInterfaces,
                                                settingPairs);

    //
    // And actually select our configuration.
    //
    status = WdfUsbTargetDeviceSelectConfig(devContext->BasicUsbUsbDevice,
                                            WDF_NO_OBJECT_ATTRIBUTES,
                                            &selectConfigParams);
    if (!NT_SUCCESS(status)) {
#if DBG
        DbgPrint("WdfUsbTargetDeviceSelectConfig failed 0x%0x\n", status);
#endif
        ExFreePool(settingPairs);
        return status;

    }

Figure 6 - Initialize WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT and Configure

Enumerating the Pipes

That's pretty much it! The good news is that we haven't been lying to our students all this time and it did work the way we thought.

The final change to the driver is to enumerate the pipes in each interface. To do this, we will have an outer loop and an inner loop. The outer loop will enumerate the configured interfaces while the inner loop will enumerate the configured pipes in the interface.

For our device, interface zero contains a bulk out and an interrupt in endpoint. Interface one contains a single isochronous out endpoint. Thus, we'll simply loop looking for configured pipes of those types and store them away in our device context.

The start of the outer loop is shown in Figure 7.

    // Since we have 2 interfaces for this configuration, we will go through each of them and get the pipes
    // in each.   NOTE:   we know that pipes are in each interface, since we defined the interfaces:
    //
    // Interface 0 has a bulk out and an interrupt pipe.
    // Interface 1 has an ISO in pipe.
    //
    // So we take advantage of that fact.
    //
    for(interfaceIndex = 0; interfaceIndex < numInterfaces; interfaceIndex++) {

        //
        // How many pipes were configure?
        //
        numPipes = WdfUsbInterfaceGetNumConfiguredPipes(
                                    settingPairs[interfaceIndex].UsbInterface);

Figure 7 - Enumerating Pipes (Outer Loop)

Note that here we leverage the fact that we have already retrieved the interface for this index and stored it in the settingPairs array.

Once we have the number of pipes configured for this interface, we can loop over the pipes looking for out bulk, interrupt, and isochronous pipes (Figure 8).

        // For all the pipes that were configured....
        //
        for(pipeIndex = 0; pipeIndex < numPipes; pipeIndex++) {

            // We'll need to find out the type the pipe, which we'll do by supplying a pipe information
            // structure when calling WdfUsbInterfaceGetConfiguredPipe
            //
            WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

            // Get the configured pipe.
            //
            configuredPipe = WdfUsbInterfaceGetConfiguredPipe(
                                    settingPairs[interfaceIndex].UsbInterface,
                                    pipeIndex,
                                    &pipeInfo);

            // First, let's see what type of pipe it is...
            //
            switch (pipeInfo.PipeType) {
                case WdfUsbPipeTypeBulk: {

                    // Bulk pipe. Determine if it's an OUT pipe
                    //
                    if (WdfUsbTargetPipeIsOutEndpoint(configuredPipe)) {

                        // Bulk OUT pipe. Should only ever get one of these...
                        //
                        ASSERT(devContext->BulkOutPipe == NULL);

                        devContext->BulkOutPipe = configuredPipe;
                    }
                    break;
                }
                case WdfUsbPipeTypeInterrupt: {
                   
                    // We're only expecting an IN interrupt pipe
                    //
                    ASSERT(WdfUsbTargetPipeIsInEndpoint(configuredPipe));

                    // And we're only expected one of them
                    //
                    ASSERT(devContext->InterruptInPipe == NULL);

                    devContext->InterruptInPipe = configuredPipe;
                    break;
                }
                case WdfUsbPipeTypeIsochronous: {
                    ASSERT(WdfUsbTargetPipeIsInEndpoint(configuredPipe));

                    ASSERT(devContext->IsochronousInPipe == NULL);

                    devContext->IsochronousInPipe = configuredPipe;
                    devContext->IsochronousInPacketSize
                                                  = pipeInfo.MaximumPacketSize;
                    break;

                }
                default: {
                   
                    // Don't know what it is, don't care what it is...                   
#if DBG
                    DbgPrint("Unexpected pipe type? 0x%x\n", pipeInfo.PipeType);
#endif               
                    break;
                }

            }

Figure 8 - Looking for Configured Pipes

And that's it!

Much to our surprise, it really is that easy. KMDF comes through again with a straightforward approach and a well thought out API.

But what about the single interface setting? We won't leave you hanging on that one. Check out the sidebar below for more information on single interface devices.

 

Sidebar: What About the Single Interface Alternate Setting?

It might be the case that you have a single interface device but would like to select an alternate setting. This also turns out to be quite simple in KMDF, requiring only one more API call.

After you have selected your configuration, all you will need do is initialize a WDF_USB_INTERFACE_SELECT_SETTING_PARAMS structure and call WdfUsbInterfaceSelectSetting to select the alternate setting.

Select the Configuration

To start we just do the usual configuration selection process and get back our configured interface:

    // The OSRFX2 device only has a single interface, so we'll
    // initialize our select configuration parameters structure
    // using the specially provided macro
    //
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_SINGLE_INTERFACE(
                                                &selectConfigParams);

    // And actually select our configuration.
    //
    status = WdfUsbTargetDeviceSelectConfig(devContext->BasicUsbUsbDevice,
                                            WDF_NO_OBJECT_ATTRIBUTES,
                                            &selectConfigParams);
    if (!NT_SUCCESS(status)) {
#if DBG
        DbgPrint("WdfUsbTargetDeviceSelectConfig failed 0x%0x\n", status);
#endif
        return status;

    }

    // Our single interface has been configured. Let's grab the
    // WDFUSBINTERFACE object so that we can get our pipes.
    //
    configuredInterface
            = selectConfigParams.Types.SingleInterface.ConfiguredUsbInterface;

Once we have our interface, we initialize our WDF_USB_INTERFACE_SELECT_SETTING_PARAMS structure using one of the available functions. By far the easiest is the WDF_USB_INTERFACE_SELECT_SETTING_PARAMS_INIT_SETTING function, which takes an interface number to enable:

    // Configure the structure with alternate setting one.
    //
    WDF_USB_INTERFACE_SELECT_SETTING_PARAMS_INIT_SETTING(
                                            &interfaceSelectSetting,
                                            1);
 And then simply select the setting for the configured interface by calling WdfUsbInterfaceSelectSetting:
    //
    // Select the setting.
    //
    status = WdfUsbInterfaceSelectSetting(configuredInterface,
                                          WDF_NO_OBJECT_ATTRIBUTES,
                                          &interfaceSelectSetting);

    if (!NT_SUCCESS(status)) {
#if DBG
        DbgPrint("WdfUsbInterfaceSelectSetting failed 0x%0x\n", status);
#endif
        ExFreePool(settingPairs);
        return status;

    }

 

 

 

 

 

User Comments
Rate this article and give us feedback. Do you find anything missing? Share your opinion with the community!
Post Your Comment

Post Your Comments.
Print this article.
Email this article.
bottom nav links