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


Перечисление USB-каналов

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

Конечная точка USB — это буфер на устройстве, в которое драйвер клиента отправляет данные или из нее получает данные. Для отправки или получения данных драйвер клиента отправляет запрос на передачу ввода-вывода в стек драйверов USB, который представляет данные на хост-контроллер. Затем контроллер узла следует определенным протоколам (в зависимости от типа конечной точки: массовая, прерванная или изохронная) для создания запросов, которые передают данные на устройство или с устройства. Все сведения о передаче данных абстрагируются от драйвера клиента. Пока драйвер клиента отправляет запрос правильного формата, стек драйверов USB обрабатывает запрос и передает данные на устройство.

Во время настройки устройства стек драйверов USB создает USB-канал (на стороне узла) для каждой конечной точки устройства, определенной в интерфейсе USB, и его активного альтернативного параметра. USB-канал — это канал связи между контроллером узла и конечной точкой. Для драйвера клиента канал — это логическая абстракция конечной точки. Чтобы отправить передачу данных, драйвер должен получить дескриптор канала, связанный с конечной точкой, которая является целевым объектом для передачи. Дескрипторы канала также требуются, когда драйвер хочет прервать передачу или сбросить канал в случае возникновения ошибок.

Все атрибуты канала являются производными от связанного дескриптора конечной точки. Например, в зависимости от типа конечной точки стек драйверов USB назначает тип для канала. Для массовой конечной точки стек драйверов USB создает массовый канал; для изохронной конечной точки создается изохронный канал и т. д. Другим важным атрибутом является объем данных, которые контроллер узла может отправить в точку конечной точки в запросе. В зависимости от этого значения драйвер клиента должен определить структуру буфера передачи.

Windows Driver Foundation (WDF) предоставляет специализированные целевые объекты ввода-вывода в среде драйвера режима ядра (KMDF) и среда выполнения платформы драйвера режима пользователя (UMDF), которые упрощают многие задачи настройки драйвера клиента. С помощью этих объектов драйвер клиента может получать сведения о текущей конфигурации, такие как количество интерфейсов, альтернативные параметры в каждом интерфейсе и их конечные точки. Один из этих объектов, называемый целевым объектом канала, выполняет задачи, связанные с конечной точкой. В этой статье описывается получение сведений о канале с помощью целевого объекта канала.

Для клиентских драйверов модели драйвера Windows (WDM) стек драйверов USB возвращает массив USBD_PIPE_INFORMATION структур. Количество элементов в массиве зависит от количества конечных точек, определенных для активного альтернативного параметра интерфейса в выбранной конфигурации. Каждый элемент содержит сведения о канале, созданном для определенной конечной точки. Сведения о выборе конфигурации и получении массива сведений о канале см. в разделе How to Select a Configuration for a USB Device.

Это важно знать

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

  • Драйвер клиента должен создать объект целевого устройства USB платформы.

    Если вы используете шаблоны USB, которые предоставляются в Microsoft Visual Studio Professional 2012, код шаблона выполняет эти задачи. Код шаблона получает дескриптор целевого объекта устройства и сохраняет его в контексте устройства.

    Драйвер клиента KMDF:

    Драйвер клиента KMDF должен получить дескриптор WDFUSBDEVICE, вызвав метод WdfUsbTargetDeviceCreateWithParameters . Дополнительные сведения см. в разделе "Исходный код устройства" статьи Общие сведения о структуре кода драйвера USB-клиента (KMDF).

    Драйвер клиента UMDF:

    Драйвер клиента UMDF должен получить указатель IWDFUsbTargetDevice , запросив объект целевого устройства платформы. Дополнительные сведения см. в разделе Реализация IPnpCallbackHardware и задачи, связанные с USB, статьи Основные сведения о структуре кода драйвера USB-клиента (UMDF).

  • Устройство должно иметь активную конфигурацию.

    Если вы используете шаблоны USB, код выбирает первую конфигурацию и альтернативный параметр по умолчанию в каждом интерфейсе. Сведения об изменении этого параметра по умолчанию см. в статье Выбор альтернативного параметра в ИНТЕРФЕЙСе USB.

    Драйвер клиента KMDF:

    Драйвер клиента KMDF должен вызывать метод WdfUsbTargetDeviceSelectConfig .

    Драйвер клиента UMDF:

    Для драйвера клиента UMDF платформа выбирает первую конфигурацию и альтернативный параметр по умолчанию для каждого интерфейса в этой конфигурации.

Получение дескрипторов USB-канала в драйвере клиента KMDF

Платформа представляет каждый канал, открытый стеком драйверов USB, в качестве объекта целевого канала USB. Драйвер клиента KMDF может получить доступ к методам целевого объекта канала для получения сведений о канале. Для передачи данных драйвер клиента должен иметь дескрипторы канала WDFUSBPIPE. Чтобы получить дескриптор канала, драйвер должен перечислить интерфейсы активной конфигурации и альтернативные параметры, а затем перечислить конечные точки, определенные в каждом параметре. Выполнение операций перечисления для каждой передачи данных может быть дорогостоящим. Поэтому один из подходов заключается в получении дескрипторов канала после настройки устройства и их хранении в контексте устройства, определяемом драйвером. Когда драйвер получает запросы на передачу данных, драйвер может получить необходимые дескрипторы канала из контекста устройства и использовать их для отправки запроса. Если драйвер клиента изменяет конфигурацию устройства, например выбирает альтернативный параметр, драйвер также должен обновить контекст устройства с помощью новых дескрипторов канала. В противном случае драйвер может ошибочно отправлять запросы на передачу на устаревшие дескрипторы канала.

Для передачи элементов управления дескрипторы канала не требуются. Для отправки запросов на передачу управления драйвер клиента WDF вызывает методы WdfUsbDevicexxxx, предоставляемые объектом устройства платформы. Для этих методов требуется дескриптор WDFUSBDEVICE для инициации передачи элементов управления, предназначенных для конечной точки по умолчанию. Для таких передач целевым объектом ввода-вывода для запроса является конечная точка по умолчанию, представленная дескриптором WDFIOTARGET, который абстрагируется дескриптором WDFUSBPIPE. На уровне устройства дескриптор WDFUSBDEVICE является абстракцией дескриптора WDFUSBPIPE для конечной точки по умолчанию.

Сведения об отправке передачи элементов управления и методах KMDF см. в статье Отправка передачи элементов управления по USB.

  1. Расширьте структуру контекста устройства для хранения дескрипторов канала.

    Если вы знаете конечные точки на устройстве, расширьте структуру контекста устройства, добавив члены WDFUSBPIPE для хранения связанных дескрипторов USB-канала. Например, можно расширить структуру контекста устройства, как показано ниже:

    typedef struct _DEVICE_CONTEXT {
        WDFUSBDEVICE    UsbDevice;
        WDFUSBINTERFACE UsbInterface;
        WDFUSBPIPE      BulkReadPipe;   // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      BulkWritePipe;  // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      InterruptPipe;  // Pipe opened for the interrupt IN endpoint.
        WDFUSBPIPE      StreamInPipe;   // Pipe opened for stream IN endpoint.
        WDFUSBPIPE      StreamOutPipe;  // Pipe opened for stream OUT endpoint.
        UCHAR           NumberConfiguredPipes;  // Number of pipes opened.
        ...
        ...                                     // Other members. Not shown.
    
    } DEVICE_CONTEXT, *PDEVICE_CONTEXT;
    
  2. Объявите структуру контекста канала.

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

    typedef struct _PIPE_CONTEXT {
    
        ULONG MaxPacketSize;
        ULONG MaxStreamsSupported;
        PUSBD_STREAM_INFORMATION StreamInfo;
    } PIPE_CONTEXT, *PPIPE_CONTEXT;
    
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PIPE_CONTEXT, GetPipeContext)
    
    

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

    Дополнительные сведения о контекстах см. в разделе Пространство контекста объекта платформы.

    Чтобы передать указатель на платформу, драйвер клиента сначала инициализирует контекст канала, вызывая WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE. Затем передает указатель на контекст канала при вызове WdfUsbTargetDeviceSelectConfig (для выбора конфигурации) или WdfUsbInterfaceSelectSetting (для выбора альтернативного параметра).

  3. После завершения запроса конфигурации устройства перечислите интерфейс и получите дескрипторы канала для настроенных каналов. Вам потребуется следующий набор сведений:

    • Дескриптор WDFUSBINTERFACE для интерфейса, содержащего текущий параметр. Этот дескриптор можно получить, перечислив интерфейсы в активной конфигурации. Кроме того, если вы указали указатель на структуру WDF_USB_DEVICE_SELECT_CONFIG_PARAMS в WdfUsbTargetDeviceSelectConfig, вы можете получить дескриптор из элемента Type.SingleInterface.ConfiguredUsbInterface (для устройств с одним интерфейсом) или элемента Type.MultiInterface.Pairs.UsbInterface (для многоинтерфейсного устройства).
    • Количество каналов, открытых для конечных точек в текущем параметре. Это число можно получить в определенном интерфейсе, вызвав метод WdfUsbInterfaceGetNumConfiguredPipes .
    • Дескрипторы WDFUSBPIPE для всех настроенных каналов. Дескриптор можно получить, вызвав метод WdfUsbInterfaceGetConfiguredPipe .

    После получения дескриптора канала драйвер клиента может вызывать методы для определения типа и направления канала. Драйвер может получить сведения о конечной точке в структуре WDF_USB_PIPE_INFORMATION . Драйвер может получить заполненную структуру, вызвав метод WdfUsbTargetPipeGetInformation . Кроме того, драйвер может указать указатель на структуру в вызове WdfUsbInterfaceGetConfiguredPipe .

В следующем примере кода перечисляются каналы в текущем параметре. Он получает дескрипторы канала для конечных точек массовых операций и прерываний устройства и сохраняет их в структуре контекста устройства драйвера. Он сохраняет максимальный размер пакета каждой конечной точки в контексте связанного канала. Если конечная точка поддерживает потоки, она открывает статические потоки, вызывая подпрограмму OpenStreams. Реализация OpenStreams показана в разделе Открытие и закрытие статических потоков в массовой конечной точке USB.

Чтобы определить, поддерживает ли конкретная массовая конечная точка статические потоки, драйвер клиента проверяет дескриптор конечной точки. Этот код реализуется во вспомогательной подпрограмме с именем RetrieveStreamInfoFromEndpointDesc, показанной в следующем блоке кода.

NTSTATUS
    FX3EnumeratePipes(
    _In_ WDFDEVICE Device)

{
    NTSTATUS                    status;
    PDEVICE_CONTEXT             pDeviceContext;
    UCHAR                       i;
    PPIPE_CONTEXT               pipeContext;
    WDFUSBPIPE                  pipe;
    WDF_USB_PIPE_INFORMATION    pipeInfo;

    PAGED_CODE();

    pDeviceContext = GetDeviceContext(Device);

    // Get the number of pipes in the current alternate setting.
    pDeviceContext->NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(
        pDeviceContext->UsbInterface);

    if (pDeviceContext->NumberConfiguredPipes == 0)
    {
        status = USBD_STATUS_BAD_NUMBER_OF_ENDPOINTS;
        goto Exit;
    }
    else
    {
        status = STATUS_SUCCESS;
    }

    // Enumerate the pipes and get pipe information for each pipe.
    for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++)
    {
        WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

        pipe =  WdfUsbInterfaceGetConfiguredPipe(
            pDeviceContext->UsbInterface,
            i,
            &pipeInfo);

        if (pipe == NULL)
        {
            continue;
        }

        pipeContext = GetPipeContext (pipe);

        // If the pipe is a bulk endpoint that supports streams,
        // If the host controller supports streams, open streams.
        // Use the endpoint as an IN bulk endpoint.
        // Store the maximum packet size.

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {

            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamInPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamInPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkReadPipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsOutEndpoint (pipe))
        {
            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamOutPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamOutPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkWritePipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {
            pDeviceContext->InterruptPipe = pipe;
            pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            continue;
        }

    }

Exit:
    return status;
}

В следующем примере кода показана вспомогательная подпрограмма с именем RetrieveStreamInfoFromEndpointDesc, которую драйвер клиента вызывает при перечислении каналов.

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

/*++
Routine Description:

This routine parses the configuration descriptor and finds the endpoint
with which the specified pipe is associated.
It then retrieves the maximum number of streams supported by the endpoint.
It stores maximum number of streams in the pipe context.

Arguments:

Device - WDFUSBDEVICE handle to the target device object.
The driver obtained that handle in a previous call to
WdfUsbTargetDeviceCreateWithParameters.

Pipe - WDFUSBPIPE handle to the target pipe object.

Return Value:

NTSTATUS
++*/

VOID RetrieveStreamInfoFromEndpointDesc (
    WDFDEVICE Device,
    WDFUSBPIPE Pipe)
{
    PDEVICE_CONTEXT                                 deviceContext                = NULL;
    PUSB_CONFIGURATION_DESCRIPTOR                   configDescriptor             = NULL;
    WDF_USB_PIPE_INFORMATION                        pipeInfo;
    PUSB_COMMON_DESCRIPTOR                          pCommonDescriptorHeader      = NULL;
    PUSB_INTERFACE_DESCRIPTOR                       pInterfaceDescriptor         = NULL;
    PUSB_ENDPOINT_DESCRIPTOR                        pEndpointDescriptor          = NULL;
    PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR   pEndpointCompanionDescriptor = NULL;
    ULONG                                           maxStreams;
    ULONG                                           index;
    BOOLEAN                                         found                        = FALSE;
    UCHAR                                           interfaceNumber = 0;
    UCHAR                                           alternateSetting = 1;
    PPIPE_CONTEXT                                   pipeContext = NULL;
    NTSTATUS                                        status;

    PAGED_CODE();

    deviceContext = GetDeviceContext (Device);
    pipeContext = GetPipeContext (Pipe);

    // Get the configuration descriptor of the currently selected configuration
    status = FX3RetrieveConfigurationDescriptor (
        deviceContext->UsbDevice,
        &deviceContext->ConfigurationNumber,
        &configDescriptor);

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not retrieve the configuration descriptor.");

        status = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    if (deviceContext->ConfigurationNumber == 1)
    {
        alternateSetting = 1;
    }
    else
    {
        alternateSetting = 0;
    }

    // Get the Endpoint Address of the pipe
    WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
    WdfUsbTargetPipeGetInformation (Pipe, &pipeInfo);

    // Parse the ConfigurationDescriptor (including all Interface and
    // Endpoint Descriptors) and locate a Interface Descriptor which
    // matches the InterfaceNumber, AlternateSetting, InterfaceClass,
    // InterfaceSubClass, and InterfaceProtocol parameters.
    pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
        configDescriptor,
        configDescriptor,
        interfaceNumber,  //Interface number is 0.
        alternateSetting,  // Alternate Setting is 1
        -1, // InterfaceClass, ignore
        -1, // InterfaceSubClass, ignore
        -1  // InterfaceProtocol, ignore
        );

    if (pInterfaceDescriptor == NULL )
    {
        // USBD_ParseConfigurationDescriptorEx failed to retrieve Interface Descriptor.
        goto Exit;
    }

    pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR) pInterfaceDescriptor;

    for(index = 0; index < pInterfaceDescriptor->bNumEndpoints; index++)
    {

        pCommonDescriptorHeader = USBD_ParseDescriptors(
            configDescriptor,
            configDescriptor->wTotalLength,
            pCommonDescriptorHeader,
            USB_ENDPOINT_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader == NULL)
        {
            // USBD_ParseDescriptors failed to retrieve Endpoint Descriptor unexpectedly.
            goto Exit;
        }

        pEndpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) pCommonDescriptorHeader;

        // Search an Endpoint Descriptor that matches the EndpointAddress
        if (pEndpointDescriptor->bEndpointAddress == pipeInfo.EndpointAddress)
        {

            found = TRUE;
            break;
        }

        // Skip the current Endpoint Descriptor and search for the next.
        pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)pCommonDescriptorHeader)
            + pCommonDescriptorHeader->bLength);
    }

    if (found)
    {
        // Locate the SuperSpeed Endpoint Companion Descriptor
        // associated with the endpoint descriptor
        pCommonDescriptorHeader = USBD_ParseDescriptors (
            configDescriptor,
            configDescriptor->wTotalLength,
            pEndpointDescriptor,
            USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader != NULL)
        {
            pEndpointCompanionDescriptor =
                (PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR) pCommonDescriptorHeader;

            maxStreams = pEndpointCompanionDescriptor->bmAttributes.Bulk.MaxStreams;

            if (maxStreams == 0)
            {
                pipeContext->MaxStreamsSupported = 0;
                pipeContext->IsStreamsCapable = FALSE;
            }
            else
            {
                pipeContext->IsStreamsCapable = TRUE;
                pipeContext->MaxStreamsSupported = 1 << maxStreams;
            }
        }
        else
        {
            KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                "USBD_ParseDescriptors failed to retrieve SuperSpeed Endpoint Companion Descriptor unexpectedly.\n" ));
        }
    }
    else
    {
        pipeContext->MaxStreamsSupported = 0;
        pipeContext->IsStreamsCapable = FALSE;
    }

Exit:
    if (configDescriptor)
    {
        ExFreePoolWithTag (configDescriptor, USBCLIENT_TAG);
    }

    return;
}

Получение дескрипторов канала в драйвере клиента UMDF

Драйвер клиента UMDF использует com-инфраструктуру и реализует классы обратного вызова COM, которые объединяются с объектами устройств платформы. Как и драйвер KMDF, драйвер клиента UMDF может получать сведения о канале только после настройки устройства. Чтобы получить сведения о канале, драйвер клиента должен получить указатель на интерфейс IWDFUsbTargetPipe объекта интерфейса платформы, содержащего активный параметр. С помощью указателя интерфейса драйвер может перечислить каналы в этом параметре, чтобы получить указатели интерфейса IWDFUsbTargetPipe , предоставляемые объектами целевого канала платформы.

Прежде чем драйвер начнет перечисление каналов, драйвер должен знать о конфигурации устройства и поддерживаемых конечных точках. На основе этой информации драйвер может хранить объекты канала в виде переменных-членов класса.

В следующем примере кода расширяется шаблон USB UMDF, который предоставляется вместе с Visual Studio Professional 2012. Описание начального кода см. в разделе Реализация IPnpCallbackHardware и задачи, относящиеся к USB, статьи Основные сведения о структуре кода драйвера usb-клиента (UMDF).

Расширьте объявление класса CDevice, как показано ниже. В этом примере кода предполагается, что устройство является платой OSR FX2. Дополнительные сведения о макете дескриптора см. в разделе Макет устройства USB.

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:
    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:
    IWDFDevice *            m_FxDevice;
    CMyIoQueue *            m_IoQueue;
    IWDFUsbTargetDevice *   m_FxUsbDevice;
    IWDFUsbInterface *      m_pIUsbInterface;  //Pointer to the target interface object.
    IWDFUsbTargetPipe *     m_pIUsbInputPipe;  // Pointer to the target pipe object for the bulk IN endpoint.
    IWDFUsbTargetPipe *     m_pIUsbOutputPipe; // Pointer to the target pipe object for the bulk OUT endpoint.
    IWDFUsbTargetPipe *     m_pIUsbInterruptPipe; // Pointer to the target pipe object for the interrupt endpoint.

private:
    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:
    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );

    HRESULT                     // Declare a helper function to enumerate pipes.
    ConfigureUsbPipes(
        );

public:
    // IPnpCallbackHardware methods
    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

В определении класса CDevice реализуйте вспомогательный метод с именем CreateUsbIoTargets. Этот метод вызывается из реализации IPnpCallbackHardware::OnPrepareHardware после того, как драйвер получил указатель на объект целевого устройства.

HRESULT  CMyDevice::CreateUsbIoTargets()
{
    HRESULT                 hr;
    UCHAR                   NumEndPoints = 0;
    IWDFUsbInterface *      pIUsbInterface = NULL;
    IWDFUsbTargetPipe *     pIUsbPipe = NULL;

    if (SUCCEEDED(hr))
    {
        UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces();

        WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces);

        hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface);
        if (FAILED(hr))
        {
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Unable to retrieve USB interface from USB Device I/O Target %!HRESULT!",
                        hr
                        );
        }
        else
        {
            m_pIUsbInterface = pIUsbInterface;

            DriverSafeRelease (pIUsbInterface); //release creation reference
        }
     }

    if (SUCCEEDED(hr))
    {
        NumEndPoints = pIUsbInterface->GetNumEndPoints();

        if (NumEndPoints != NUM_OSRUSB_ENDPOINTS)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Has %d endpoints, expected %d, returning %!HRESULT! ",
                        NumEndPoints,
                        NUM_OSRUSB_ENDPOINTS,
                        hr
                        );
        }
    }

    if (SUCCEEDED(hr))
    {
        for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++)
        {
            hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex,
                                                  &pIUsbPipe);

            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_ERROR,
                            TEST_TRACE_DEVICE,
                            "%!FUNC! Unable to retrieve USB Pipe for PipeIndex %d, %!HRESULT!",
                            PipeIndex,
                            hr
                            );
            }
            else
            {
                if ( pIUsbPipe->IsInEndPoint() )
                {
                    if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInterruptPipe = pIUsbPipe;
                    }
                    else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInputPipe = pIUsbPipe;
                    }
                    else
                    {
                        pIUsbPipe->DeleteWdfObject();
                    }
                }
                else if ( pIUsbPipe->IsOutEndPoint() && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) )
                {
                    m_pIUsbOutputPipe = pIUsbPipe;
                }
                else
                {
                    pIUsbPipe->DeleteWdfObject();
                }

                DriverSafeRelease(pIUsbPipe);  //release creation reference
            }
        }

        if (NULL == m_pIUsbInputPipe || NULL == m_pIUsbOutputPipe)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Input or output pipe not found, returning %!HRESULT!",
                        hr
                        );
        }
    }

    return hr;
}

В UMDF драйвер клиента использует индекс канала для отправки запросов на передачу данных. Индекс канала — это число, назначаемое стеком драйверов USB при открытии каналов для конечных точек в параметре. Чтобы получить индекс канала, вызовите метод**IWDFUsbTargetPipe::GetInformation**. Метод заполняет структуру WINUSB_PIPE_INFORMATION . Значение PipeId указывает индекс канала.

Одним из способов выполнения операций чтения и записи в целевом канале является вызов IWDFUsbInterface::GetWinUsbHandle , чтобы получить дескриптор WinUSB, а затем вызвать функции WinUSB. Например, драйвер может вызывать функцию WinUsb_ReadPipe или WinUsb_WritePipe . В этих вызовах функций драйвер должен указать индекс канала. Дополнительные сведения см. в разделе Как получить доступ к USB-устройству с помощью функций WinUSB.

Конвейерные дескрипторы для клиентских драйверов на основе WDM

После выбора конфигурации стек USB-драйвера настраивает канал к каждой конечной точке устройства. Стек драйверов USB возвращает массив USBD_PIPE_INFORMATION структур. Количество элементов в массиве зависит от количества конечных точек, определенных для активного альтернативного параметра интерфейса в выбранной конфигурации. Каждый элемент содержит сведения о канале, созданном для определенной конечной точки. Дополнительные сведения о получении дескрипторов канала см. в статье Выбор конфигурации для USB-устройства.

Чтобы создать запрос на передачу ввода-вывода, драйвер клиента должен иметь дескриптор канала, связанного с этой конечной точкой. Драйвер клиента может получить дескриптор канала из элемента PipeHandleUSBD_PIPE_INFORMATION в массиве.

Помимо дескриптора канала, драйверу клиента также требуется тип канала. Драйвер клиента может определить тип канала, проверив элемент PipeType .

В зависимости от типа конечной точки стек драйверов USB поддерживает различные типы каналов. Драйвер клиента может определить тип канала, проверив элемент PipeTypeUSBD_PIPE_INFORMATION. Для различных типов каналов требуются разные типы блоков запросов USB (URB) для выполнения транзакций ввода-вывода.

Затем драйвер клиента отправляет URB в стек драйверов USB. Стек драйверов USB обрабатывает запрос и отправляет указанные данные в запрошенный целевой канал.

URB содержит сведения о запросе, такие как целевой дескриптор канала, буфер передачи и его длина. Каждая структура в союзе URB использует определенные члены: TransferFlags, TransferBuffer, TransferBufferLength и TransferBufferMDL. В элементе TransferFlags есть флаги для конкретных типов, которые соответствуют каждому типу URB. Для всех urob-объектов передачи данных флаг USBD_TRANSFER_DIRECTION_IN в TransferFlags указывает направление передачи. Клиентские драйверы устанавливают флаг USBD_TRANSFER_DIRECTION_IN для чтения данных с устройства. Драйверы снимите этот флаг для отправки данных на устройство. Данные могут считываться или записываться в буфер, расположенный в памяти, или в MDL. В любом случае драйвер указывает размер буфера в элементе TransferBufferLength . Драйвер предоставляет резидентный буфер в элементе TransferBuffer и MDL в элементе TransferBufferMDL . Независимо от того, что предоставляет драйвер, другой должен иметь значение NULL.