Поделиться через


Общие сведения о структуре кода драйвера 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 и EvtDedfallback.

В коде шаблона драйвер клиента регистрируется для двух событий: 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 драйвер клиента должен выполнить следующие задачи:

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

    Компоненты Windows, PnP и диспетчеры питания отправляют запросы, связанные с устройством, драйверам в ответ на изменения состояния PnP (например, запущено, остановлено и удалено) и состояния питания (например, работа или приостановка). Для драйверов на основе KMDF платформа перехватывает эти запросы. Драйвер клиента может получать уведомления о запросах, регистрируя подпрограммы обратного вызова с именем обратного вызова PnP power eventbacks с помощью вызова 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 содержит следующий блок кода, использующий pragma, чтобы alloc_text указать, что реализация драйвера EvtDevicePrepareHardware находится в доступной для страниц памяти.

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

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

Целевой объект USB-ввода-вывода (дескриптор) Получение дескриптора путем вызова... Description
Объект 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, если:

    Такие драйверы не требуются для указания версии контракта клиента, поэтому необходимо пропустить шаг 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-устройством, приложение сначала открывает дескриптор устройства, вызывая подпрограммы SetDixxxx и 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-элемента управления".