Share via


Supporting query interface in KMDF

In my last post I talked about bidirectional interfaces which can have both input and output parameters. KMDF supports both unidirectional and bidirectional interfaces. As is the case with many KMDF APIs and behaviors, we allow you to implement the easy pattern with little to no work and let you opt in to more complicated functionality as needed. Query interface support follows this philosophy.

If you want to support a unidirectional interface which only contains output parameters, you have very little code to write. First, declare and initialize the interface as you would want it to be returned to the requestor.  Second, declare and initialize a WDF_QUERY_INTERFACE_CONFIG structure with your interface.  Finally, you call WdfDeviceAddQueryInterface(). Here is a snippet from the WDK KMDF toaster bus sample which demonstrates how easy this is

 WDF_QUERY_INTERFACE_CONFIG  qiConfig;
TOASTER_INTERFACE_STANDARD  ToasterInterface;

//
// Create a copy of the interface that the framework will use when
// completing the query interface request.
//
RtlZeroMemory(&ToasterInterface, sizeof(ToasterInterface));

ToasterInterface.InterfaceHeader.Size = sizeof(ToasterInterface);
ToasterInterface.InterfaceHeader.Version = 1;
ToasterInterface.InterfaceHeader.Context = (PVOID) hChild;

//
// Let the framework handle reference counting.
//
ToasterInterface.InterfaceHeader.InterfaceReference =
    WdfDeviceInterfaceReferenceNoOp;
ToasterInterface.InterfaceHeader.InterfaceDereference =
    WdfDeviceInterfaceDereferenceNoOp;

ToasterInterface.GetCrispinessLevel  = Bus_GetCrispinessLevel;
ToasterInterface.SetCrispinessLevel  = Bus_SetCrispinessLevel;
ToasterInterface.IsSafetyLockEnabled = Bus_IsSafetyLockEnabled;

WDF_QUERY_INTERFACE_CONFIG_INIT(&qiConfig,
                                (PINTERFACE) &ToasterInterface,
                                &GUID_TOASTER_INTERFACE_STANDARD,
                                NULL);

status = WdfDeviceAddQueryInterface(hChild, &qiConfig);
if (!NT_SUCCESS(status)) {
    return status;
}

But what about other scenarios? There are 3 other patterns that apply to handling a query interface request:

  1. You want to selectively succeed, fail, or pass down the query interface request based on state or previous requests
  2. You want to capture input parameters in the request and optionally set output parameters
  3. You want forward the request down to the parent stack (PDO only)

Selectively succeed, fail, or pass down the request

Typically a query interface request is processed regardless of the previous queries or device state, but not always. For instance, let's say you want to only handle out the interface to one requestor (it would be available once it is dereferenced). Another example might be that you want to determine the status of the query interface request based on the capabilities exposed lower in the device stack. Finally, to process the query interface, you might be required to allocate memory (which could lead to failure if you cannot allocate the required buffer). To allow your driver to determine the status, check state, perform your custom action, etc. you specify a EvtDeviceProcessQueryInterfaceRequest function pointer in your WDF_QUERY_INTERFACE_CONFIG structure after calling WDF_QUERY_INTERFACE_CONFIG_INIT(). To succeed the query interface request, return a value which is NT_SUCCESS(), likewise to fail a request, return a value which is !NT_SUCCESS(). To instruct the framework to pass down the request to the lower stack, you return a special value, STATUS_NOT_SUPPORTED.

Capturing input parameters in the request

There is a field in WDF_QUERY_INTERFACE_CONFIG which controls how the framework treats the interface buffer itself. If ImportInterface is FALSE (the default value), the interface buffer is treated as a pure output buffer and the framework will copy over the interface to return directly into the requestor's buffer (and subsequently overwriting any values the requestor initialized the buffer with). If ImportInterface is TRUE, the framework does not touch the requestor's buffer. This allows you to inspect the requestor's buffer first and copy data from it. By setting ImportInterface to TRUE, there are two direct side effects that you must deal with. First, you must specify a EvtDeviceProcessQueryInterfaceRequest function when adding the interface. Second, and most importantly, your driver must now copy all output values into the requestor's buffer because the framework no longer does this for you!

Forwarding the request to the parent's stack

There are some query interface requests that must be forwarded from the PDO down to the parent's stack (and most likely to be completed by the parent's own PDO). For example, look at usbccgp (the USB generic parent driver). This driver does not support every single USBHUB interface, yet it must maintain compatibility with the hub's supported interfaces. To do this, it forwards all unhandled requests down from the PDO it created down the parent's stack. The framework can automatically provide this functionality to your driver if you set SendQueryToParentStack to TRUE.