Общие сведения о структуре кода драйвера USB-клиента (KMDF)

В этом разделе вы узнаете о исходном коде драйвера USB-клиента на основе KMDF. Примеры кода создаются шаблоном драйвера пользовательского режима USB, включенным в Microsoft Visual Studio 2019.

В этих разделах содержатся сведения о коде шаблона.

Инструкции по созданию кода шаблона KMDF см. в статье "Как написать первый ДРАЙВЕР USB-клиента (KMDF)".

Исходный код драйвера

Объект драйвера представляет экземпляр клиентского драйвера после загрузки драйвера в памяти Windows. Полный исходный код для объекта драйвера находится в Driver.h и Driver.c.

Driver.h

Прежде чем обсуждать сведения о коде шаблона, давайте рассмотрим некоторые объявления в файле заголовка (Driver.h), которые относятся к разработке драйверов KMDF.

Driver.h содержит эти файлы, включенные в комплект драйверов 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"

Файлы заголовков Ntddk.h и Wdf.h всегда включаются в разработку драйверов KMDF. Файл заголовка содержит различные объявления и определения методов и структур, необходимые для компиляции драйвера KMDF.

Usb.h и Usbdlib.h включают объявления и определения структур и подпрограмм, необходимых драйверу клиента для USB-устройства.

Wdfusb.h включает объявления и определения структур и методов, необходимых для взаимодействия с целевыми объектами USB-ввода-вывода, предоставляемыми платформой.

Device.h, Queue.h и Trace.h не включены в WDK. Эти файлы заголовков создаются шаблоном и рассматриваются далее в этом разделе.

Следующий блок в Driver.h предоставляет объявления типов ролей функций для подпрограммы DriverEntry, а также для подпрограмм обратного вызова событий EvtDriverDeviceAdd и EvtCleanupCallback. Все эти подпрограммы реализуются драйвером. Типы ролей помогают статическим средствам проверки драйверов (SDV) анализировать исходный код драйвера. Дополнительные сведения о типах ролей см. в разделе "Объявление функций с помощью типов ролей функций для драйверов KMDF".

DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;

Файл реализации Driver.c содержит следующий блок кода, использующий alloc_text pragma, чтобы указать, находятся ли функция DriverEntry и подпрограммы обратного вызова событий в страничной памяти.

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

Обратите внимание, что DriverEntry помечается как INIT, а подпрограммы обратного вызова событий помечены как PAGE. В разделе INIT указывается, что исполняемый код DriverEntry доступен для страницы и удаляется сразу после возвращения драйвера из DriverEntry. Раздел PAGE указывает, что код не должен оставаться в физической памяти все время; его можно записать в файл страницы, если он не используется. Дополнительные сведения см. в разделе "Блокировка страничного кода" или "Данные".

Вскоре после загрузки драйвера Windows выделяет DRIVER_OBJECT структуру, представляющую драйвер. Затем он вызывает функцию входа драйвера DriverEntry и передает указатель на структуру. Так как Windows ищет подпрограмму по имени, каждый драйвер должен реализовать подпрограмму с именем DriverEntry. Подпрограмма выполняет задачи инициализации драйвера и задает подпрограммы обратного вызова события драйвера в платформу.

В следующем примере кода показана подпрограмма DriverEntry, созданная шаблоном.

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;
}

Подпрограмма DriverEntry имеет два параметра: указатель на DRIVER_OBJECT структуру, выделенную Windows, и путь реестра для драйвера. Параметр RegistryPath представляет путь для конкретного драйвера в реестре.

В подпрограмме DriverEntry драйвер выполняет следующие задачи:

  • Выделяет глобальные ресурсы, необходимые во время существования драйвера. Например, в коде шаблона драйвер клиента выделяет ресурсы, необходимые для трассировки программного обеспечения WPP, вызывая макрос WPP_INIT_TRACING .

  • Регистрирует определенные подпрограммы обратного вызова событий с помощью платформы.

    Чтобы зарегистрировать обратные вызовы событий, драйвер клиента сначала указывает указатели на свои реализации подпрограмм в EvtDriverXxx в определенных структурах WDF. Затем драйвер вызывает метод WdfDriverCreate и предоставляет эти структуры (рассматривается на следующем шаге).

  • Вызывает метод WdfDriverCreate и извлекает дескриптор объекта драйвера фреймворка.

    После того как драйвер клиента вызывает WdfDriverCreate, фреймворк создает объект драйвера фреймворка для представления драйвера клиента. По завершении вызова драйвер клиента получает дескриптор WDFDRIVER и может получить сведения о драйвере, например его путь к реестру, сведения о версии и т. д. (см . справочник по объекту драйвера WDF).

    Обратите внимание, что объект драйвера платформы отличается от объекта драйвера Windows, описанного DRIVER_OBJECT. В любое время драйвер клиента может получить указатель на структуру WindowsDRIVER_OBJECT с помощью дескриптора WDFDRIVER и вызова метода WdfGetDriver.

После вызова WdfDriverCreate платформа сотрудничает с драйвером клиента для взаимодействия с Windows. Платформа выступает в качестве уровня абстракции между Windows и драйвером и обрабатывает большинство сложных задач драйвера. Драйвер клиента регистрируется в платформе для получения интересующих его событий. При возникновении определенных событий Windows уведомляет платформу. Если драйвер зарегистрировал обратный вызов события для определенного события, платформа уведомляет драйвер, вызвав зарегистрированный обратный вызов события. При необходимости драйвер получает возможность обрабатывать событие. Если драйвер не зарегистрировал обратный вызов событий, платформа продолжает обработку события по умолчанию.

Одним из обратных вызовов событий, которые должен зарегистрировать драйвер, является EvtDriverDeviceAdd. Платформа вызывает реализацию EvtDriverDeviceAdd драйвера, когда платформа готова к созданию объекта устройства. В Windows объект устройства является логическим представлением функции физического устройства, для которого загружается драйвер клиента (рассматривается далее в этом разделе).

Другие обратные вызовы событий, которые драйвер может зарегистрировать: EvtDriverUnload, EvtCleanupCallback и EvtDestroyCallback.

В коде шаблона драйвер клиента регистрируется для двух событий: EvtDriverDeviceAdd и EvtCleanupCallback. Драйвер указывает указатель на его реализацию EvtDriverDeviceAdd в структуре WDF_DRIVER_CONFIG и обратном вызове событий EvtCleanupCallback в структуре WDF_OBJECT_ATTRIBUTES .

Когда Windows готова освободить структуру DRIVER_OBJECT и выгрузить драйвер, платформа сообщает об этом событии драйверу клиента, вызывая реализацию EvtCleanupCallback драйвера. Платформа вызывает обратный вызов непосредственно перед удалением объекта драйвера платформы. Драйвер клиента может освободить все глобальные ресурсы, которые он выделил в DriverEntry. Например, в коде шаблона клиентский драйвер останавливает трассировку WPP, которая была активирована в DriverEntry.

В следующем примере кода показана реализация обратного вызова обратного вызова события evtCleanupCallback драйвера клиента.

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) );

}

После того как устройство распознается стеком USB-драйверов, драйвер шины создает объект физического устройства (PDO) для устройства и связывает PDO с узлом устройства. Узел устройства находится в стеке, где PDO находится в нижней части. Каждый стек должен иметь один PDO и может иметь объекты устройств (фильтрация DOs) и объект устройства-функции (FDO) над ним. Дополнительные сведения см. в разделе "Узлы устройств" и "Стеки устройств".

На этом рисунке показан стек устройств для драйвера шаблона MyUSBDriver_.sys.

стек устройств для шаблонного драйвера.

Обратите внимание на стек устройств с именем "Мое USB-устройство". Стек USB-драйверов создает PDO для стека устройств. В примере PDO связан с Usbhub3.sys, который является одним из драйверов, включенных в стек USB-драйверов. В качестве функционального драйвера для устройства клиентский драйвер должен сначала создать FDO для устройства, а затем присоединить его на вершину стека устройств.

Для драйвера клиента на основе KMDF платформа выполняет эти задачи от имени драйвера клиента. Для представления FDO для устройства в рамках создается объект устройства каркаса. Однако драйвер клиента может указать определенные параметры инициализации, используемые платформой для настройки нового объекта. Эта возможность предоставляется драйверу клиента, когда платформа вызывает реализацию EvtDriverDeviceAdd драйвера. После создания объекта и присоединения FDO к вершине стека устройств, фреймворк предоставляет клиентскому драйверу дескриптор WDFDEVICE для объекта устройства фреймворка. С помощью этого дескриптора драйвер клиента может выполнять различные операции, связанные с устройствами.

В следующем примере кода показана реализация события обратного вызова EvtDriverDeviceAdd для драйвера клиента.

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;
}

Во время выполнения реализация EvtDriverDeviceAdd использует макрос PAGED_CODE для проверки того, что подпрограмма вызывается в соответствующей среде для страничного кода. Убедитесь, что макрос вызывается после объявления всех переменных; В противном случае компиляция завершается ошибкой, так как созданные исходные файлы — это C-файлы, а не .cpp файлы.

Реализация EvtDriverDeviceAdd в драйвере клиента вызывает вспомогательную функцию MyUSBDriver_CreateDevice для выполнения необходимых задач.

В следующем примере кода показана вспомогательная функция MyUSBDriver_CreateDevice. MyUSBDriver_CreateDevice определен в 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 имеет два параметра: дескриптор объекта драйвера платформы, созданного в предыдущем вызове DriverEntry, и указатель на структуру WDFDEVICE_INIT . Платформа выделяет структуру WDFDEVICE_INIT и передает указатель, чтобы драйвер клиента мог заполнить структуру параметрами инициализации для создаваемого объекта устройства платформы.

В реализации EvtDriverDeviceAdd драйвер клиента должен выполнить следующие задачи:

  • Вызовите метод WdfDeviceCreate, чтобы получить дескриптор WDFDEVICE для нового объекта устройства.

    Метод WdfDeviceCreate приводит к созданию объекта устройства платформы для FDO и присоединению его к верхней части стека устройств. В вызове WdfDeviceCreate драйвер клиента должен выполнить следующие задачи:

    • Укажите указатели на функции обратного вызова мощности драйвера клиента Plug and play (PnP), указанные в структуре WDFDEVICE_INIT, заданной каркасом. Подпрограммы сначала задаются в структуре WDF_PNPPOWER_EVENT_CALLBACKS, а затем связаны с WDFDEVICE_INIT путем вызова метода WdfDeviceInitSetPnpPowerEventCallbacks.

    Компоненты Windows, PnP и диспетчеры питания отправляют запросы, связанные с устройством, драйверам в ответ на изменения состояния PnP (например, запущено, остановлено и удалено) и состояния питания (например, работа или приостановка). Для драйверов на основе KMDF платформа перехватывает эти запросы. Драйвер клиента может получать уведомления о запросах, регистрируя подпрограммы обратного вызова, называемые PnP power event callbacks, с помощью вызова WdfDeviceCreate. Когда компоненты Windows отправляют запросы, платформа обрабатывает их и вызывает соответствующий обратный вызов событий питания PnP, если драйвер клиента зарегистрирован.

    Одной из подпрограмм обратного вызова событий питания PnP, которые должен реализовать драйвер клиента, является EvtDevicePrepareHardware. Обратный вызов этого события вызывается при запуске устройства диспетчером PnP. Реализация EvtDevicePrepareHardware рассматривается в следующем разделе.

    • Укажите указатель на структуру контекста устройства драйвера. Указатель должен быть задан в структуре WDF_OBJECT_ATTRIBUTES, которая инициализирована путем вызова макроса WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE.

    Контекст устройства (иногда называемый расширением устройства) — это структура данных (определяемая драйвером клиента) для хранения сведений о конкретном объекте устройства. Драйвер клиента передает указатель на его контекст устройства в фреймворк. Платформа выделяет блок памяти на основе размера структуры и сохраняет указатель на это расположение памяти в объекте устройства платформы. Драйвер клиента может использовать указатель для доступа и хранения информации в элементах контекста устройства. Дополнительные сведения о контекстах устройств см. в разделе "Пространство контекста объектов Платформы".

    После завершения вызова WdfDeviceCreate драйвер клиента получает дескриптор нового объекта устройства платформы, который сохраняет указатель на блок памяти, выделенный платформой для контекста устройства. Теперь драйвер клиента может получить указатель на контекст устройства, вызвав макрос WdfObjectGet_DEVICE_CONTEXT .

  • Зарегистрируйте GUID интерфейса устройства для драйвера клиента, вызвав метод WdfDeviceCreateDeviceInterface. Приложения могут взаимодействовать с драйвером с помощью этого GUID. Константа GUID объявлена в заголовке public.h.

  • Настройте очереди для передачи ввода-вывода на устройство. Код шаблона определяет MyUSBDriver_QueueInitialize, вспомогательная подпрограмма для настройки очередей, которая рассматривается в разделе исходного кода очереди.

Исходный код устройства

Объект устройства представляет экземпляр устройства, для которого драйвер клиента загружается в память. Полный исходный код для объекта устройства находится в Device.h и Device.c.

Device.h

Файл заголовка Device.h включает заголовочный файл public.h, содержащий общие объявления, используемые всеми файлами проекта.

Следующий блок в Device.h объявляет контекст устройства для драйвера клиента.

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

} DEVICE_CONTEXT, *PDEVICE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)

Структура DEVICE_CONTEXT определяется драйвером клиента и сохраняет сведения об объекте устройства платформы. Он определяется в Device.h и содержит два члена: дескриптор объекта USB-устройства целевого устройства фреймворка (рассмотрен позже) и заполнитель. Эта структура будет расширена в последующих упражнениях.

Device.h также включает макрос WDF_DECLARE_CONTEXT_TYPE , который создает встроенную функцию, WdfObjectGet_DEVICE_CONTEXT. Драйвер клиента может вызвать такую функцию, чтобы получить указатель на блок памяти из объекта устройства платформы.

Следующая строка кода объявляет MyUSBDriver_CreateDevice, вспомогательную функцию, которая получает дескриптор WDFUSBDEVICE для объекта USB-целевое устройство.

NTSTATUS
MyUSBDriver_CreateDevice(
    _Inout_ PWDFDEVICE_INIT DeviceInit
    );

USBCreate принимает указатель на структуру WDFDEVICE_INIT в качестве параметра. Это тот же указатель, который был передан платформой при вызове реализации Драйвера клиента EvtDriverDeviceAdd. В основном MyUSBDriver_CreateDevice выполняет задачи EvtDriverDeviceAdd. Исходный код для реализации EvtDriverDeviceAdd рассматривается в предыдущем разделе.

Следующая строка в Device.h объявляет тип роли функции для обратной функции вызова EvtDevicePrepareHardware. Обратный вызов событий реализуется драйвером клиента и выполняет такие задачи, как настройка USB-устройства.

EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;

Device.c

Файл реализации Device.c содержит следующий блок кода, который использует alloc_text pragma, чтобы указать, что реализация EvtDevicePrepareHardware драйвера находится в памяти с возможностью разбиения на страницы.

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

В реализации EvtDevicePrepareHardware драйвер клиента выполняет задачи инициализации, относящиеся к USB. К этим задачам относятся регистрация драйвера клиента, инициализация целевых объектов ввода-вывода для USB и выбор конфигурации USB. В следующей таблице показаны специализированные целевые объекты ввода-вывода, предоставляемые платформой. Дополнительные сведения см. в разделе "Целевые объекты usb-ввода-вывода".

Целевой объект USB-ввода-вывода (дескриптор) Получите дескриптор, вызвав... Описание
Объект USB целевого устройства (WDFUSBDEVICE) WdfUsbTargetDeviceCreateWithParameters Представляет USB-устройство и предоставляет методы для извлечения дескриптора устройства и отправки запросов управления на устройство.
Объект целевого интерфейса USB (WDFUSBINTERFACE) WdfUsbTargetDeviceGetInterface Представляет отдельный интерфейс и предоставляет методы, которые драйвер клиента может вызывать для выбора альтернативного параметра и получения сведений о параметре.
Объект целевого канала USB (WDFUSBPIPE) WdfUsbInterfaceGetConfiguredPipe Представляет отдельный канал для конечной точки, настроенной в текущем альтернативном параметре для интерфейса. Стек USB-драйверов выбирает каждый интерфейс в выбранной конфигурации и настраивает канал связи для каждой конечной точки в интерфейсе. В терминологии USB этот канал связи называется каналом связи.

В этом примере кода показана реализация для 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;
}

Ниже приведен более подробный обзор задач драйвера клиента, реализованных в коде шаблона:

  1. Указывает версию соглашения клиентского драйвера при подготовке к регистрации в нижележащем стеке драйверов USB, загружаемом Windows.

    Windows может загружать стек драйверов USB 3.0 или USB 2.0 в зависимости от контроллера узла, к которому подключено USB-устройство. Стек драйверов USB 3.0 является новым в Windows 8 и поддерживает несколько новых функций, определенных спецификацией USB 3.0, например возможности потоков. Новый стек драйверов также реализует несколько улучшений, таких как улучшение отслеживания и обработки блоков USB-запросов (URBS), которые доступны с помощью нового набора подпрограмм URB. Драйвер клиента, который намерен использовать эти функции или вызвать новые подпрограммы, должен указать версию контракта USBD_CLIENT_CONTRACT_VERSION_602. Драйвер клиента USBD_CLIENT_CONTRACT_VERSION_602 должен соответствовать определенному набору правил. Дополнительные сведения об этих правилах см. в статье "Рекомендации по использованию URI".

    Чтобы указать версию контракта, драйвер клиента должен инициализировать структуру WDF_USB_DEVICE_CREATE_CONFIG с версией контракта, вызвав макрос WDF_USB_DEVICE_CREATE_CONFIG_INIT.

  2. Вызывает метод WdfUsbTargetDeviceCreateWithParameters. Для этого метода требуется дескриптор объекта устройства фреймворка, который клиентский драйвер получил ранее, вызвав WdfDeviceCreate в реализации драйвера EvtDriverDeviceAdd. Метод WdfUsbTargetDeviceCreateWithParameters:

    • Регистрирует драйвер клиента в базовом стеке USB-драйверов.
    • Извлекает дескриптор WDFUSBDEVICE для объекта целевого USB-устройства, созданного фреймворком. Код шаблона сохраняет дескриптор объекта usb-целевого устройства в контексте устройства. Используя этот дескриптор, драйвер клиента может получить информацию, специфическую для USB-устройства.

    Необходимо вызвать WdfUsbTargetDeviceCreate вместо WdfUsbTargetDeviceCreateWithParameters, если:

    • Драйвер клиента не вызывает новый набор подпрограмм URB, доступных в версии WDK версииWindows 8.

      Если драйвер клиента вызывает WdfUsbTargetDeviceCreateWithParameters, стек USB-драйверов предполагает, что все URB выделяются путем вызова WdfUsbTargetDeviceCreateUrb или WdfUsbTargetDeviceCreateIsochUrb. URI, выделенные этими методами, имеют непрозрачные блоки контекста URB, используемые стеком USB-драйверов для ускорения обработки. Если драйвер клиента использует URB, который не был выделен этими методами, USB-драйвер вызывает bugcheck.

      Дополнительные сведения о выделении URB см. в разделе "Выделение и создание URB".

    • Драйвер клиента не планирует придерживаться набора правил, описанных в Лучшие практики: Использование URB.

    Такие драйверы не требуются для указания версии контракта клиента, поэтому необходимо пропустить шаг 1.

  3. Выбирает конфигурацию USB.

    В коде шаблона драйвер клиента выбирает конфигурацию по умолчанию на USB-устройстве. Конфигурация по умолчанию включает конфигурацию 0 устройства и альтернативный параметр 0 каждого интерфейса в этой конфигурации.

    Чтобы выбрать конфигурацию по умолчанию, драйвер клиента настраивает структуру WDF_USB_DEVICE_SELECT_CONFIG_PARAMS путем вызова функции WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES . Функция инициализирует элемент Type в WdfUsbTargetDeviceSelectConfigTypeMultiInterface , чтобы указать, что если доступны несколько интерфейсов, необходимо выбрать альтернативный параметр в каждом из этих интерфейсов. Так как вызов должен выбрать конфигурацию по умолчанию, драйвер клиента задает ЗНАЧЕНИЕ NULL в параметре SettingPairs и 0 в параметре NumberInterfaces. По завершении член MultiInterface.NumberOfConfiguredInterfaces структуры WDF_USB_DEVICE_SELECT_CONFIG_PARAMS указывает количество интерфейсов, для которых была выбрана альтернативная настройка 0. Другие члены не изменяются.

    Примечание. Если драйвер клиента хочет выбрать альтернативные параметры, отличные от параметра по умолчанию, драйвер должен создать массив WDF_USB_INTERFACE_SETTING_PAIR структур. Каждый элемент в массиве задает определяемый устройством номер интерфейса и индекс альтернативного параметра для выбора. Эти сведения хранятся в дескрипторах конфигурации и интерфейса устройства, которые можно получить путем вызова метода WdfUsbTargetDeviceRetrieveConfigDescriptor. Затем драйвер клиента должен вызвать WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES и передать массив WDF_USB_INTERFACE_SETTING_PAIR в платформу.

Исходный код очереди

Объект очереди фреймворка представляет очередь ввода-вывода для определенного объекта устройства фреймворка. Полный исходный код для объекта очереди находится в Queue.h и Queue.c.

Queue.h

Объявляет подпрограмму обратного вызова события для события, вызываемого объектом очереди платформы.

Первый блок в Queue.h объявляет контекст очереди.

typedef struct _QUEUE_CONTEXT {

    ULONG PrivateDeviceData;  // just a placeholder

} QUEUE_CONTEXT, *PQUEUE_CONTEXT;

WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)

Как и контекст устройства, контекст очереди — это структура данных, определенная клиентом для хранения сведений о определенной очереди.

Следующая строка кода объявляет функцию MyUSBDriver_QueueInitialize, вспомогательную функцию, которая создаёт и инициализирует объект очереди платформы.

NTSTATUS
MyUSBDriver_QueueInitialize(
    _In_ WDFDEVICE Device
    );

Следующий пример кода объявляет роль функции для обратного вызова EvtIoDeviceControl. Обратный вызов события реализуется драйвером клиента и вызывается при обработке платформы запроса на управление устройством ввода-вывода.

EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;

Queue.c

Файл реализации Queue.c содержит следующий блок кода, использующий alloc_text pragma, чтобы указать, что реализация драйвера MyUSBDriver_QueueInitialize находится в памяти, доступной для страниц.

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

WDF предоставляет объект очереди платформы для обработки потока запроса драйверу клиента. Платформа создает объект очереди платформы, когда драйвер клиента вызывает метод WdfIoQueueCreate. В этом вызове драйвер клиента может указать определенные параметры конфигурации перед созданием очередей фреймворка. Эти параметры включают, управляется ли очередь питанием, позволяет ли она запросы нулевой длины, или является ли очередью по умолчанию для драйвера. Один объект очереди платформы может обрабатывать несколько типов запросов, таких как управление чтением, записью и устройством ввода-вывода. Драйвер клиента может указать обратные вызовы событий для каждого из этих запросов.

Драйвер клиента также должен указать тип отправки. Тип отправки объекта очереди определяет, как платформа отправляет запросы драйверу клиента. Механизм доставки может быть последовательным, параллельным или пользовательским, определенным драйвером клиента. Для последовательной очереди запрос не доставляется до тех пор, пока драйвер клиента не завершит предыдущий запрос. В параллельном режиме отправки платформа пересылает запросы сразу после их поступления от диспетчера ввода-вывода. Это означает, что драйвер клиента может получать один запрос во время обработки другого. В пользовательском механизме клиент вручную извлекает следующий запрос из объекта очереди платформы, когда драйвер готов обработать его.

Как правило, клиентский драйвер должен настроить очереди в обработчике события EvtDriverDeviceAdd. Код шаблона предоставляет вспомогательную подпрограмму MyUSBDriver_QueueInitialize, которая инициализирует объект очереди фреймворка.

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;
}

Чтобы настроить очереди, драйвер клиента выполняет следующие задачи:

  1. Задает параметры конфигурации очереди в структуре WDF_IO_QUEUE_CONFIG . Код шаблона использует функцию WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE для инициализации структуры. Функция указывает объект очереди в качестве очереди по умолчанию, контролирует энергопотребление и получает запросы параллельно.
  2. Добавляет обратные вызовы событий драйвера клиента для запросов ввода-вывода для очереди. В шаблоне клиентский драйвер указывает указатель на обратный вызов события для запроса управления ввода-вывода устройства.
  3. Вызывает WdfIoQueueCreate , чтобы получить дескриптор WDFQUEUE к объекту очереди платформы, созданному платформой.

Вот как работает механизм очереди. Чтобы взаимодействовать с USB-устройством, приложение сначала открывает дескриптор устройства, вызывая подпрограммы SetDixxx и CreateHandle. С помощью этого дескриптора приложение вызывает функцию DeviceIoControl с определенным кодом элемента управления. В зависимости от типа кода элемента управления приложение может указать входные и выходные буферы в этом вызове. Вызов в конечном итоге получается диспетчером операций ввода-вывода, который затем создает запрос (IRP) и пересылает его драйверу клиента. Платформа перехватывает запрос, создает объект запроса платформы и добавляет его в объект очереди платформы. В этом случае, так как драйвер клиента зарегистрировал обратный вызов событий для запроса элемента управления ввода-вывода устройства, платформа вызывает обратный вызов. Кроме того, так как объект очереди был создан с флагом WdfIoQueueDispatchParallel, обратный вызов вызывается сразу после добавления запроса в очередь.

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;
}

Когда платформа вызывает обратный вызов события драйвера клиента, он передает дескриптор объекту запроса платформы, который содержит запрос (и его входные и выходные буферы), отправленные приложением. Кроме того, он отправляет дескриптор объекта очереди фреймворка, содержащий запрос. При обратном вызове события драйвер клиента обрабатывает запрос по мере необходимости. Код шаблона просто завершает запрос. Драйвер клиента может выполнять более задействованные задачи. Например, если приложение запрашивает определенную информацию об устройстве, в обратном вызове события драйвер клиента может создать запрос на usb-управление и отправить его в стек USB-драйверов, чтобы получить запрошенные сведения об устройстве. Запросы USB-управления обсуждаются в разделе "Передача USB-элемента управления".