Compartir a través de


Descripción de la estructura del código del controlador de cliente USB (KMDF)

En este tema, obtendrá información sobre el código fuente de un controlador de cliente USB basado en KMDF. Los ejemplos de código se generan mediante la plantilla de controlador del modo de usuario USB incluida con Microsoft Visual Studio 2019.

En estas secciones se proporciona información sobre el código de plantilla.

Para obtener instrucciones sobre cómo generar el código de plantilla de KMDF, consulte Cómo escribir su primer controlador de cliente USB (KMDF).

Código fuente del controlador

El objeto de controlador representa la instancia del controlador cliente después de que Windows cargue el controlador en memoria. El código fuente completo del objeto de controlador está en Driver.h y Driver.c.

Driver.h

Antes de analizar los detalles del código de plantilla, echemos un vistazo a algunas declaraciones en el archivo de encabezado (Driver.h) que son relevantes para el desarrollo de controladores KMDF.

Driver.h, contiene estos archivos, incluidos en el Kit de controladores de Windows (WDK).

#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>

#include "device.h"
#include "queue.h"
#include "trace.h"

Los archivos de encabezado Ntddk.h y Wdf.h siempre se incluyen para el desarrollo de controladores KMDF. El archivo de encabezado incluye varias declaraciones y definiciones de métodos y estructuras que necesita para compilar un controlador KMDF.

Usb.h y Usbdlib.h incluyen declaraciones y definiciones de estructuras y rutinas requeridas por un controlador cliente para un dispositivo USB.

Wdfusb.h incluye declaraciones y definiciones de estructuras y métodos necesarios para comunicarse con los objetos de destino de E/S USB proporcionados por el marco.

Device.h, Queue.h y Trace.h no se incluyen en el WDK. Estos archivos de encabezado se generan mediante la plantilla y se describen más adelante en este tema.

El siguiente bloque de Driver.h proporciona declaraciones de tipo de rol de función para la rutina DriverEntry y EvtDriverDeviceAdd y EvtCleanupCallback rutinas de devolución de llamada de eventos. El controlador implementa todas estas rutinas. Los tipos de rol ayudan a Static Driver Verifier (SDV) a analizar el código fuente de un controlador. Para obtener más información sobre los tipos de roles, vea Declarar funciones mediante tipos de rol de función para controladores KMDF.

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;

El archivo de implementación Driver.c contiene el siguiente bloque de código que usa alloc_text pragma para especificar si la función DriverEntry y las rutinas de devolución de llamada de eventos están en memoria paginable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif

Observe que DriverEntry está marcado como INIT, mientras que las rutinas de devolución de llamada de eventos se marcan como PAGE. La sección INIT indica que el código ejecutable de DriverEntry es paginable y descartado en cuanto el controlador vuelve de su DriverEntry. La sección PAGE indica que el código no tiene que permanecer en memoria física todo el tiempo; se puede escribir en el archivo de página cuando no está en uso. Para obtener más información, vea Bloquear código paginable o datos.

Poco después de cargar el controlador, Windows asigna una estructura de DRIVER_OBJECT que representa el controlador. A continuación, llama a la rutina de punto de entrada del controlador, DriverEntry y pasa un puntero a la estructura. Dado que Windows busca la rutina por nombre, cada controlador debe implementar una rutina denominada DriverEntry. La rutina realiza las tareas de inicialización del controlador y especifica las rutinas de devolución de llamada de eventos del controlador en el marco de trabajo.

En el ejemplo de código siguiente se muestra la rutina DriverEntry generada por la plantilla.

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    WDF_DRIVER_CONFIG config;
    NTSTATUS status;
    WDF_OBJECT_ATTRIBUTES attributes;

    //
    // Initialize WPP Tracing
    //
    WPP_INIT_TRACING( DriverObject, RegistryPath );

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Register a cleanup callback so that we can call WPP_CLEANUP when
    // the framework driver object is deleted during driver unload.
    //
    WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
    attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;

    WDF_DRIVER_CONFIG_INIT(&config,
                           MyUSBDriver_EvtDeviceAdd
                           );

    status = WdfDriverCreate(DriverObject,
                             RegistryPath,
                             &attributes,
                             &config,
                             WDF_NO_HANDLE
                             );

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
        WPP_CLEANUP(DriverObject);
        return status;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

La rutina DriverEntry tiene dos parámetros: un puntero a la estructura DRIVER_OBJECT asignada por Windows y una ruta de acceso del Registro para el controlador. El parámetro RegistryPath representa la ruta de acceso específica del controlador en el Registro.

En la rutina DriverEntry , el controlador realiza estas tareas:

  • Asigna los recursos globales necesarios durante la vigencia del controlador. Por ejemplo, en el código de plantilla, el controlador cliente asigna los recursos necesarios para el seguimiento de software de WPP mediante una llamada a la macro WPP_INIT_TRACING .

  • Registra ciertas rutinas de devolución de llamada de eventos con el marco de trabajo.

    Para registrar las devoluciones de llamada de eventos, el controlador cliente especifica primero punteros a sus implementaciones de las rutinas EvtDriverXxx en determinadas estructuras de WDF. A continuación, el controlador llama al método WdfDriverCreate y proporciona esas estructuras (descritas en el paso siguiente).

  • Llama al método WdfDriverCreate y recupera un identificador para el objeto del controlador de marco.

    Una vez que el controlador cliente llama a WdfDriverCreate, el marco crea un objeto de controlador de marco para representar el controlador cliente. Cuando se completa la llamada, el controlador cliente recibe un identificador WDFDRIVER y puede recuperar información sobre el controlador, como su ruta de acceso del Registro, la información de la versión, etc. (vea Referencia de objetos de controlador WDF).

    Tenga en cuenta que el objeto del controlador de marco es diferente del objeto de controlador de Windows descrito por DRIVER_OBJECT. En cualquier momento, el controlador de cliente puede obtener un puntero a la estructurade DRIVER_OBJECT de Windows mediante el identificador WDFDRIVER y la llamada al método WdfGetDriver .

Después de la llamada A WdfDriverCreate , el marco se asocia con el controlador cliente para comunicarse con Windows. El marco actúa como una capa de abstracción entre Windows y el controlador, y controla la mayoría de las tareas complicadas del controlador. El controlador cliente se registra con el marco de trabajo para los eventos en los que está interesado el controlador. Cuando se producen determinados eventos, Windows notifica al marco. Si el controlador registró una devolución de llamada de evento para un evento determinado, el marco notifica al controlador invocando la devolución de llamada del evento registrado. Al hacerlo, el controlador tiene la oportunidad de controlar el evento, si es necesario. Si el controlador no registró su devolución de llamada de eventos, el marco continúa con su control predeterminado del evento.

Una de las devoluciones de llamada de eventos que el controlador debe registrar es EvtDriverDeviceAdd. El marco invoca la implementación evtDriverDeviceAdd del controlador cuando el marco está listo para crear un objeto de dispositivo. En Windows, un objeto de dispositivo es una representación lógica de la función del dispositivo físico para el que se carga el controlador cliente (que se describe más adelante en este tema).

Otras devoluciones de llamada de eventos que el controlador puede registrar son EvtDriverUnload, EvtCleanupCallback y EvtDestroyCallback.

En el código de plantilla, el controlador cliente se registra para dos eventos: EvtDriverDeviceAdd y EvtCleanupCallback. El controlador especifica un puntero a la implementación de EvtDriverDeviceAdd en la estructura WDF_DRIVER_CONFIG y la devolución de llamada de eventos EvtCleanupCallback en la estructura de WDF_OBJECT_ATTRIBUTES .

Cuando Windows esté listo para liberar la estructura de DRIVER_OBJECT y descargar el controlador, el marco notifica ese evento al controlador cliente invocando la implementación evtCleanupCallback del controlador. El marco invoca esa devolución de llamada justo antes de eliminar el objeto del controlador de marco. El controlador cliente puede liberar todos los recursos globales que asignó en driverEntry. Por ejemplo, en el código de plantilla, el controlador cliente detiene el seguimiento de WPP que se activó en DriverEntry.

En el ejemplo de código siguiente se muestra la implementación de devolución de llamada de eventos EvtCleanupCallback del controlador cliente.

VOID MyUSBDriver_EvtDriverContextCleanup(
    _In_ WDFDRIVER Driver
    )
{
    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE ();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    //
    // Stop WPP Tracing
    //
    WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );

}

Una vez que la pila de controladores USB reconoce el dispositivo, el controlador de bus crea un objeto de dispositivo físico (PDO) para el dispositivo y asocia el PDO con el nodo del dispositivo. El nodo de dispositivo está en una formación de pila, donde el PDO está en la parte inferior. Cada pila debe tener un PDO y puede tener objetos de dispositivo de filtro (DO de filtro) y un objeto de dispositivo de función (FDO) encima de él. Para obtener más información, consulte Nodos de dispositivo y pilas de dispositivos.

En esta ilustración se muestra la pila de dispositivos del controlador de plantilla, MyUSBDriver_.sys.

pila de dispositivos para el controlador de plantilla.

Observe la pila de dispositivos denominada "Mi dispositivo USB". La pila del controlador USB crea el PDO para la pila de dispositivos. En el ejemplo, el PDO está asociado a Usbhub3.sys, que es uno de los controladores incluidos en la pila de controladores USB. Como controlador de función para el dispositivo, el controlador cliente primero debe crear el FDO para el dispositivo y, a continuación, asociarlo a la parte superior de la pila de dispositivos.

En el caso de un controlador cliente basado en KMDF, el marco realiza esas tareas en nombre del controlador cliente. Para representar el FDO del dispositivo, el marco crea un objeto de dispositivo de marco. Sin embargo, el controlador de cliente puede especificar determinados parámetros de inicialización que usa el marco para configurar el nuevo objeto. Esa oportunidad se da al controlador cliente cuando el marco invoca la implementación EvtDriverDeviceAdd del controlador. Una vez creado el objeto y el FDO se conecta a la parte superior de la pila de dispositivos, el marco proporciona al controlador cliente un identificador WDFDEVICE al objeto de dispositivo de marco. Con este identificador, el controlador cliente puede realizar varias operaciones relacionadas con el dispositivo.

En el ejemplo de código siguiente se muestra la implementación de devolución de llamada de eventos EvtDriverDeviceAdd del controlador cliente.

NTSTATUS
MyUSBDriver_EvtDeviceAdd(
    _In_    WDFDRIVER       Driver,
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    NTSTATUS status;

    UNREFERENCED_PARAMETER(Driver);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = MyUSBDriver_CreateDevice(DeviceInit);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Durante el tiempo de ejecución, la implementación de EvtDriverDeviceAdd usa la macro PAGED_CODE para comprobar que se llama a la rutina en un entorno adecuado para el código paginable. Asegúrese de llamar a la macro después de declarar todas las variables; De lo contrario, se produce un error en la compilación porque los archivos de origen generados son archivos .c y no .cpp.

La implementación evtDriverDeviceAdd del controlador cliente llama a la función auxiliar MyUSBDriver_CreateDevice para realizar las tareas necesarias.

En el ejemplo de código siguiente se muestra la función auxiliar MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice se define en Device.c.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    )
{
    WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status)) {
        //
        // Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
        // inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
        // device.h header file. This function will do the type checking and return
        // the device context. If you pass a wrong object  handle
        // it will return NULL and assert if run under framework verifier mode.
        //
        deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
        deviceContext->PrivateDeviceData = 0;

        //
        // Create a device interface so that applications can find and talk
        // to us.
        //
        status = WdfDeviceCreateDeviceInterface(
            device,
            &GUID_DEVINTERFACE_MyUSBDriver_,
            NULL // ReferenceString
            );

        if (NT_SUCCESS(status)) {
            //
            // Initialize the I/O Package and any Queues
            //
            status = MyUSBDriver_QueueInitialize(device);
        }
    }

    return status;
}

EvtDriverDeviceAdd tiene dos parámetros: un identificador para el objeto de controlador de marco creado en la llamada anterior a DriverEntry y un puntero a una estructura de WDFDEVICE_INIT . El marco asigna la estructura WDFDEVICE_INIT y la pasa un puntero para que el controlador de cliente pueda rellenar la estructura con parámetros de inicialización para que se cree el objeto de dispositivo de marco.

En la implementación EvtDriverDeviceAdd , el controlador cliente debe realizar estas tareas:

  • Llame al método WdfDeviceCreate para recuperar un identificador WDFDEVICE al nuevo objeto de dispositivo.

    El método WdfDeviceCreate hace que el marco cree un objeto de dispositivo de marco para el FDO y lo adjunte a la parte superior de la pila de dispositivos. En la llamada WdfDeviceCreate , el controlador cliente debe realizar estas tareas:

    Los componentes de Windows, PnP y los administradores de energía, envían solicitudes relacionadas con el dispositivo a los controladores en respuesta a los cambios en el estado PnP (por ejemplo, iniciado, detenido y quitado) y el estado de alimentación (como trabajar o suspender). En el caso de los controladores basados en KMDF, el marco intercepta esas solicitudes. El controlador cliente puede recibir notificaciones sobre las solicitudes mediante el registro de rutinas de devolución de llamada denominadas devoluciones de llamada de eventos de energía PnP con el marco, mediante la llamada WdfDeviceCreate . Cuando los componentes de Windows envían solicitudes, el marco los controla y llama a la devolución de llamada de evento de energía PnP correspondiente, si el controlador de cliente se ha registrado.

    Una de las rutinas de devolución de llamada de eventos de energía PnP que el controlador cliente debe implementar es EvtDevicePrepareHardware. Esa devolución de llamada de evento se invoca cuando el administrador de PnP inicia el dispositivo. La implementación de EvtDevicePrepareHardware se describe en la sección siguiente.

    Un contexto de dispositivo (a veces denominado extensión de dispositivo) es una estructura de datos (definida por el controlador de cliente) para almacenar información sobre un objeto de dispositivo específico. El controlador de cliente pasa un puntero a su contexto de dispositivo al marco de trabajo. El marco asigna un bloque de memoria basado en el tamaño de la estructura y almacena un puntero a esa ubicación de memoria en el objeto de dispositivo del marco. El controlador de cliente puede usar el puntero para acceder a la información y almacenarla en los miembros del contexto del dispositivo. Para obtener más información sobre los contextos de dispositivo, vea Espacio de contexto de objeto de marco.

    Una vez completada la llamada WdfDeviceCreate , el controlador cliente recibe un identificador para el nuevo objeto de dispositivo de marco, que almacena un puntero al bloque de memoria asignado por el marco para el contexto del dispositivo. El controlador de cliente ahora puede obtener un puntero al contexto del dispositivo llamando a la macro WdfObjectGet_DEVICE_CONTEXT .

  • Registre un GUID de interfaz de dispositivo para el controlador cliente llamando al método WdfDeviceCreateDeviceInterface . Las aplicaciones pueden comunicarse con el controlador mediante este GUID. La constante GUID se declara en el encabezado public.h.

  • Configure colas para transferencias de E/S al dispositivo. El código de plantilla define MyUSBDriver_QueueInitialize, una rutina auxiliar para configurar colas, que se describe en la sección Código fuente de cola .

Código fuente del dispositivo

El objeto de dispositivo representa la instancia del dispositivo para el que se carga el controlador de cliente en memoria. El código fuente completo del objeto de dispositivo está en Device.h y Device.c.

Device.h

El archivo de encabezado Device.h incluye public.h, que contiene declaraciones comunes usadas por todos los archivos del proyecto.

El siguiente bloque de Device.h declara el contexto del dispositivo para el controlador de cliente.

typedef struct _DEVICE_CONTEXT
{
    WDFUSBDEVICE UsbDevice;
    ULONG PrivateDeviceData;  // just a placeholder

} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)

El controlador de cliente define la estructura DEVICE_CONTEXT y almacena información sobre un objeto de dispositivo de marco. Se declara en Device.h y contiene dos miembros: un identificador para el objeto de dispositivo de destino USB de un marco (descrito más adelante) y un marcador de posición. Esta estructura se expandirá en ejercicios posteriores.

Device.h también incluye la macro WDF_DECLARE_CONTEXT_TYPE , que genera una función insertada, WdfObjectGet_DEVICE_CONTEXT. El controlador cliente puede llamar a esa función para recuperar un puntero al bloque de memoria del objeto de dispositivo de marco.

La siguiente línea de código declara MyUSBDriver_CreateDevice, una función auxiliar que recupera un identificador WDFUSBDEVICE al objeto de dispositivo de destino USB.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    );

USBCreate toma un puntero a una estructura de WDFDEVICE_INIT como parámetro. Este es el mismo puntero que pasó el marco cuando invocó la implementación evtDriverDeviceAdd del controlador cliente. Básicamente, MyUSBDriver_CreateDevice realiza las tareas de EvtDriverDeviceAdd. El código fuente de la implementación EvtDriverDeviceAdd se describe en la sección anterior.

La siguiente línea de Device.h declara una declaración de tipo de rol de función para la rutina de devolución de llamada de eventos EvtDevicePrepareHardware . El controlador cliente implementa la devolución de llamada de eventos y realiza tareas como configurar el dispositivo USB.

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Device.c

El archivo de implementación device.c contiene el siguiente bloque de código que usa alloc_text pragma para especificar que la implementación del controlador de EvtDevicePrepareHardware está en memoria paginable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif

En la implementación de EvtDevicePrepareHardware, el controlador cliente realiza las tareas de inicialización específicas de USB. Estas tareas incluyen registrar el controlador de cliente, inicializar objetos de destino de E/S específicos de USB y seleccionar una configuración USB. En la tabla siguiente se muestran los objetos de destino de E/S especializados proporcionados por el marco de trabajo. Para obtener más información, consulte Destinos de E/S USB.

Objeto de destino de E/S USB (identificador) Para obtener un identificador, llame a... Descripción
Objeto de dispositivo de destino USB (WDFUSBDEVICE ) WdfUsbTargetDeviceCreateWithParameters Representa un dispositivo USB y proporciona métodos para recuperar el descriptor de dispositivo y enviar solicitudes de control al dispositivo.
Objeto de interfaz de destino USB (WDFUSBINTERFACE ) WdfUsbTargetDeviceGetInterface Representa una interfaz individual y proporciona métodos a los que un controlador cliente puede llamar para seleccionar una configuración alternativa y recuperar información sobre la configuración.
Objeto de canalización de destino USB (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe Representa una canalización individual para un punto de conexión configurado en la configuración alternativa actual de una interfaz. La pila del controlador USB selecciona cada interfaz de la configuración seleccionada y configura un canal de comunicación para cada punto de conexión dentro de la interfaz. En la terminología usb, ese canal de comunicación se conoce como canalización.

En este ejemplo de código se muestra la implementación de EvtDevicePrepareHardware.

NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
    _In_ WDFDEVICE Device,
    _In_ WDFCMRESLIST ResourceList,
    _In_ WDFCMRESLIST ResourceListTranslated
    )
{
    NTSTATUS status;
    PDEVICE_CONTEXT pDeviceContext;
    WDF_USB_DEVICE_CREATE_CONFIG createParams;
    WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;

    UNREFERENCED_PARAMETER(ResourceList);
    UNREFERENCED_PARAMETER(ResourceListTranslated);

    PAGED_CODE();

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");

    status = STATUS_SUCCESS;
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);

    if (pDeviceContext->UsbDevice == NULL) {

        //
        // Specifying a client contract version of 602 enables us to query for
        // and use the new capabilities of the USB driver stack for Windows 8.
        // It also implies that we conform to rules mentioned in the documentation
        // documentation for WdfUsbTargetDeviceCreateWithParameters.
        //
        WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
                                         USBD_CLIENT_CONTRACT_VERSION_602
                                         );

        status = WdfUsbTargetDeviceCreateWithParameters(Device,
                                                    &createParams,
                                                    WDF_NO_OBJECT_ATTRIBUTES,
                                                    &pDeviceContext->UsbDevice
                                                    );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
            return status;
        }

        //
        // Select the first configuration of the device, using the first alternate
        // setting of each interface
        //
        WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
                                                                     0,
                                                                     NULL
                                                                     );
        status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
                                                WDF_NO_OBJECT_ATTRIBUTES,
                                                &configParams
                                                );

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
            return status;
        }
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");

    return status;
}

Esta es una visión más detallada de las tareas del controlador cliente implementadas por el código de plantilla:

  1. Especifica la versión del contrato del controlador cliente como preparación para registrarse en la pila de controladores USB subyacente, cargada por Windows.

    Windows puede cargar la pila de controladores USB 3.0 o USB 2.0, en función del controlador host al que está conectado el dispositivo USB. La pila de controladores USB 3.0 es nueva en Windows 8 y admite varias características nuevas definidas por la especificación USB 3.0, como la funcionalidad de flujos. La nueva pila de controladores también implementa varias mejoras, como un mejor seguimiento y procesamiento de bloques de solicitudes USB (URB), que están disponibles a través de un nuevo conjunto de rutinas URB. Un controlador cliente que pretende usar esas características o llamar a las nuevas rutinas debe especificar la versión del contrato de USBD_CLIENT_CONTRACT_VERSION_602. Un controlador de cliente USBD_CLIENT_CONTRACT_VERSION_602 debe cumplir un determinado conjunto de reglas. Para obtener más información sobre esas reglas, consulte Procedimientos recomendados: Uso de direcciones URL.

    Para especificar la versión del contrato, el controlador cliente debe inicializar una estructura de WDF_USB_DEVICE_CREATE_CONFIG con la versión del contrato llamando a la macro WDF_USB_DEVICE_CREATE_CONFIG_INIT .

  2. Llama al método WdfUsbTargetDeviceCreateWithParameters . El método requiere un identificador para el objeto de dispositivo de marco que el controlador cliente obtuvo anteriormente llamando a WdfDeviceCreate en la implementación del controlador de EvtDriverDeviceAdd. El método WdfUsbTargetDeviceCreateWithParameters :

    • Registra el controlador cliente con la pila de controladores USB subyacente.
    • Recupera un identificador WDFUSBDEVICE en el objeto de dispositivo de destino USB creado por el marco. El código de plantilla almacena el identificador en el objeto de dispositivo de destino USB en su contexto de dispositivo. Con ese identificador, el controlador cliente puede obtener información específica de USB sobre el dispositivo.

    Debe llamar a WdfUsbTargetDeviceCreate en lugar de WdfUsbTargetDeviceCreateWithParameters si:

    Estos controladores no son necesarios para especificar una versión del contrato de cliente y, por tanto, deben omitir el paso 1.

  3. Selecciona una configuración USB.

    En el código de plantilla, el controlador cliente selecciona la configuración predeterminada en el dispositivo USB. La configuración predeterminada incluye la configuración 0 del dispositivo y el valor alternativo 0 de cada interfaz dentro de esa configuración.

    Para seleccionar la configuración predeterminada, el controlador cliente configura la estructura de WDF_USB_DEVICE_SELECT_CONFIG_PARAMS llamando a la función WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES . La función inicializa el miembro Type en WdfUsbTargetDeviceSelectConfigTypeMultiInterface para indicar que, si hay varias interfaces disponibles, se debe seleccionar una configuración alternativa en cada una de esas interfaces. Dado que la llamada debe seleccionar la configuración predeterminada, el controlador de cliente especifica NULL en el parámetro SettingPairs y 0 en el parámetro NumberInterfaces . Al finalizar, el miembro MultiInterface.NumberOfConfiguredInterfaces de WDF_USB_DEVICE_SELECT_CONFIG_PARAMS indica el número de interfaces para las que se seleccionó El valor alternativo 0. Otros miembros no se modifican.

    Nota Si el controlador cliente desea seleccionar una configuración alternativa distinta de la predeterminada, el controlador debe crear una matriz de estructuras de WDF_USB_INTERFACE_SETTING_PAIR . Cada elemento de la matriz especifica el número de interfaz definido por el dispositivo y el índice de la configuración alternativa que se va a seleccionar. Esa información se almacena en los descriptores de interfaz y configuración del dispositivo que se pueden obtener llamando al método WdfUsbTargetDeviceRetrieveConfigDescriptor . A continuación, el controlador de cliente debe llamar a WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES y pasar la matriz de WDF_USB_INTERFACE_SETTING_PAIR al marco.

Código fuente de cola

El objeto de cola del marco representa la cola de E/S para un objeto de dispositivo de marco específico. El código fuente completo del objeto queue está en Queue.h y Queue.c.

Queue.h

Declara una rutina de devolución de llamada de evento para el evento generado por el objeto de cola del marco.

El primer bloque de Queue.h declara un contexto de cola.

typedef struct _QUEUE_CONTEXT {

    ULONG PrivateDeviceData;  // just a placeholder

} QUEUE_CONTEXT, *PQUEUE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

De forma similar a un contexto de dispositivo, un contexto de cola es una estructura de datos definida por el cliente para almacenar información sobre una cola determinada.

La siguiente línea de código declara MyUSBDriver_QueueInitialize función, la función auxiliar que crea e inicializa el objeto de cola de marco.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    );

En el siguiente ejemplo de código se declara una declaración de tipo de rol de función para la rutina de devolución de llamada de eventos EvtIoDeviceControl . El controlador cliente implementa la devolución de llamada de eventos y se invoca cuando el marco procesa una solicitud de control de E/S de dispositivo.

EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;

Queue.c

El archivo de implementación, Queue.c, contiene el siguiente bloque de código que usa alloc_text pragma para especificar que la implementación del controlador de MyUSBDriver_QueueInitialize está en memoria paginable.

#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif

WDF proporciona el objeto de cola del marco para controlar el flujo de solicitud al controlador de cliente. El marco crea un objeto de cola de marco cuando el controlador cliente llama al método WdfIoQueueCreate . En esa llamada, el controlador de cliente puede especificar determinadas opciones de configuración antes de que el marco cree colas. Estas opciones incluyen si la cola está administrada por energía, permite solicitudes de longitud cero o es la cola predeterminada para el controlador. Un único objeto de cola de marco puede controlar varios tipos de solicitudes, como el control de E/S de lectura, escritura y dispositivo. El controlador cliente puede especificar devoluciones de llamada de eventos para cada una de esas solicitudes.

El controlador cliente también debe especificar el tipo de envío. El tipo de envío de un objeto de cola determina cómo el marco entrega solicitudes al controlador de cliente. El mecanismo de entrega puede ser secuencial, en paralelo o mediante un mecanismo personalizado definido por el controlador de cliente. Para una cola secuencial, una solicitud no se entrega hasta que el controlador cliente completa la solicitud anterior. En modo de envío paralelo, el marco reenvía las solicitudes en cuanto llegan del administrador de E/S. Esto significa que el controlador cliente puede recibir una solicitud mientras se procesa otra. En el mecanismo personalizado, el cliente extrae manualmente la siguiente solicitud del objeto de cola del marco cuando el controlador está listo para procesarlo.

Normalmente, el controlador cliente debe configurar colas en la devolución de llamada de eventos EvtDriverDeviceAdd del controlador. El código de plantilla proporciona la rutina auxiliar, MyUSBDriver_QueueInitialize, que inicializa el objeto de cola del marco.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    )
{
    WDFQUEUE queue;
    NTSTATUS status;
    WDF_IO_QUEUE_CONFIG    queueConfig;

    PAGED_CODE();
    
    //
    // Configure a default queue so that requests that are not
    // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
    // other queues get dispatched here.
    //
    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
         &queueConfig,
        WdfIoQueueDispatchParallel
        );

    queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;

    status = WdfIoQueueCreate(
                 Device,
                 &queueConfig,
                 WDF_NO_OBJECT_ATTRIBUTES,
                 &queue
                 );

    if( !NT_SUCCESS(status) ) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
        return status;
    }

    return status;
}

Para configurar colas, el controlador cliente realiza estas tareas:

  1. Especifica las opciones de configuración de la cola en una estructura de WDF_IO_QUEUE_CONFIG . El código de plantilla usa la función WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE para inicializar la estructura. La función especifica el objeto queue como el objeto de cola predeterminado, está administrado por energía y recibe solicitudes en paralelo.
  2. Agrega las devoluciones de llamada de eventos del controlador cliente para las solicitudes de E/S de la cola. En la plantilla, el controlador cliente especifica un puntero a su devolución de llamada de evento para una solicitud de control de E/S de dispositivo.
  3. Llama a WdfIoQueueCreate para recuperar un identificador WDFQUEUE al objeto de cola del marco creado por el marco.

Este es el funcionamiento del mecanismo de cola. Para comunicarse con el dispositivo USB, una aplicación abre primero un identificador para el dispositivo mediante una llamada a las rutinas SetDixxx y CreateHandle. Mediante este identificador, la aplicación llama a la función DeviceIoControl con un código de control específico. Según el tipo de código de control, la aplicación puede especificar búferes de entrada y salida en esa llamada. Finalmente, el Administrador de E/S recibe la llamada, que luego crea una solicitud (IRP) y la reenvía al controlador cliente. El marco intercepta la solicitud, crea un objeto de solicitud de marco y lo agrega al objeto de cola del marco. En este caso, dado que el controlador cliente registró su devolución de llamada de eventos para la solicitud de control de E/S del dispositivo, el marco invoca la devolución de llamada. Además, dado que el objeto queue se creó con la marca WdfIoQueueDispatchParallel, la devolución de llamada se invoca en cuanto se agrega la solicitud a la cola.

VOID
MyUSBDriver_EvtIoDeviceControl(
    _In_ WDFQUEUE Queue,
    _In_ WDFREQUEST Request,
    _In_ size_t OutputBufferLength,
    _In_ size_t InputBufferLength,
    _In_ ULONG IoControlCode
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, 
                TRACE_QUEUE, 
                "!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d", 
                Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);

    WdfRequestComplete(Request, STATUS_SUCCESS);

    return;
}

Cuando el marco invoca la devolución de llamada de eventos del controlador cliente, pasa un identificador al objeto de solicitud del marco que contiene la solicitud (y sus búferes de entrada y salida) enviadas por la aplicación. Además, envía un identificador al objeto de cola del marco que contiene la solicitud. En la devolución de llamada de evento, el controlador cliente procesa la solicitud según sea necesario. El código de plantilla simplemente completa la solicitud. El controlador cliente puede realizar tareas más implicadas. Por ejemplo, si una aplicación solicita cierta información del dispositivo, en la devolución de llamada del evento, el controlador cliente puede crear una solicitud de control USB y enviarla a la pila del controlador USB para recuperar la información del dispositivo solicitada. Las solicitudes de control USB se describen en Transferencia de control USB.