Открытие и закрытие статических потоков в конечной точке массовой передачи USB

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

На usb 2.0 и более ранних устройствах массовая конечная точка может отправлять или получать один поток данных через конечную точку. На устройствах USB 3.0 массовые конечные точки имеют возможность отправлять и получать несколько потоков данных через конечную точку.

Стек USB-драйверов корпорации Майкрософт в Windows поддерживает несколько потоков. Это позволяет драйверу клиента отправлять независимые запросы ввода-вывода в каждый поток, связанный с массовой конечной точкой на устройстве USB 3.0. Запросы к разным потокам не сериализуются.

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

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

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

Платформа драйверов в режиме ядра (KMDF) не поддерживает статические потоки встроенным образом. Драйвер клиента должен отправлять URB в стиле Windows Driver Model (WDM), которые открывают и закрывают потоки. В этой статье описывается, как форматировать и отправлять такие URB. Драйвер клиентский в User Mode Driver Framework (UMDF) не может использовать возможность статических потоков.

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

Предпосылки

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

  • Вызывается метод WdfUsbTargetDeviceCreateWithParameters .

    Для этого метода требуется USBD_CLIENT_CONTRACT_VERSION_602 версию контракта клиента. Указывая, что версия драйвера клиента должна соответствовать набору правил. Дополнительные сведения см. в статье "Рекомендации по использованию URI".

    Вызов извлекает дескриптор WDFUSBDEVICE для целевого объекта USB-устройства фреймворка. Этот дескриптор требуется для последующих вызовов открытых потоков. Как правило, драйвер клиента регистрируется в процедуре обратного вызова событий драйвера EVT_WDF_DEVICE_PREPARE_HARDWARE.

    Драйверы WDM: Вызовите процедуру USBD_CreateHandle и получите USBD-дескриптор для регистрации драйвера в стеке USB-драйверов.

  • Настроили устройство и получили дескриптор канала WDFUSBPIPE в массовую конечную точку, которая поддерживает потоки. Чтобы получить дескриптор канала, вызовите метод WdfUsbInterfaceGetConfiguredPipe в текущем альтернативном параметре интерфейса в выбранной конфигурации.

    Драйверы WDM: Получите дескриптор потока USBD, отправив запрос на выбор конфигурации или интерфейса. Дополнительные сведения см. в разделе "Выбор конфигурации для USB-устройства".

Как открыть статические потоки

  1. Определите, поддерживает ли базовый стек USB-драйверов и контроллер узла возможность статических потоков, вызвав метод WdfUsbTargetDeviceQueryUsbCapability . Как правило, драйвер клиента вызывает функцию в обработчике событий драйвера EVT_WDF_DEVICE_PREPARE_HARDWARE.

    Драйверы WDM: Вызовите процедуру USBD_QueryUsbCapability . Как правило, драйвер запрашивает возможности, которые он хочет использовать в подпрограмме начального устройства драйвера (IRP_MN_START_DEVICE). Пример кода см. в USBD_QueryUsbCapability.

    Укажите следующие сведения:

    • Дескриптор объекта USB-устройства, полученный в предыдущем вызове WdfUsbTargetDeviceCreateWithParameters, для регистрации драйвера клиента.

      Драйверы WDM: Передайте дескриптор USBD, который был получен при предыдущем вызове функции USBD_CreateHandle.

      Если драйвер клиента хочет использовать определенную возможность, драйвер должен сначала запросить базовый стек USB-драйверов, чтобы определить, поддерживает ли стек драйверов и контроллер узла. Если возможность поддерживается, только тогда драйвер должен отправить запрос на использование возможности. Для некоторых запросов требуются URI,такие как возможность потоков (рассмотренная на шаге 5). Для этих запросов убедитесь, что вы используете тот же дескриптор для запроса возможностей и выделения URI. Это связано с тем, что стек драйверов использует дескрипторы для отслеживания поддерживаемых возможностей, которые может использовать драйвер.

      Например, если вы получили USBD_HANDLE (вызывая USBD_CreateHandle), запросите стек драйверов путем вызова USBD_QueryUsbCapability и выделите URB путем вызова USBD_UrbAllocate. Передайте один и тот же USBD_HANDLE в эти вызовы.

      При вызове методов KMDF WdfUsbTargetDeviceQueryUsbCapability и WdfUsbTargetDeviceCreateUrb укажите тот же дескриптор WDFUSBDEVICE для целевого объекта платформы в этих вызовах методов.

    • Назначенный GUID для GUID_USB_CAPABILITY_STATIC_STREAMS.

    • Выходной буфер (указатель на USHORT). По завершении буфер заполняется максимальным числом потоков (на конечную точку), поддерживаемым контроллером узла.

    • Длина в байтах выходного буфера. Для потоков длина равна sizeof (USHORT).

  2. Оцените возвращаемое значение NTSTATUS. Если подпрограмма успешно завершена, возвращается STATUS_SUCCESS, поддерживается возможность статических потоков. В противном случае метод возвращает соответствующий код ошибки.

  3. Определите количество потоков, которые нужно открыть. Максимальное количество открытых потоков ограничено:

    • Максимальное количество потоков, поддерживаемых контроллером узла. Это число получено WdfUsbTargetDeviceQueryUsbCapability (для драйверов WDM, USBD_QueryUsbCapability), в буфере выходных данных, предоставленном вызывающим объектом. Стек USB-драйверов, предоставляемый Корпорацией Майкрософт, поддерживает до 255 потоков. WdfUsbTargetDeviceQueryUsbCapability учитывает это ограничение при вычислении количества потоков. Метод никогда не возвращает значение, превышающее 255.
    • Максимальное количество потоков, поддерживаемых конечной точкой на устройстве. Чтобы получить это число, проверьте дескриптор компаньона конечной точки (см. USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR в Usbspec.h). Чтобы получить дескриптор компаньона конечной точки, необходимо проанализировать дескриптор конфигурации. Чтобы получить дескриптор конфигурации, драйвер клиента должен вызвать метод WdfUsbTargetDeviceRetrieveConfigDescriptor . Необходимо использовать вспомогательные подпрограммы, USBD_ParseConfigurationDescriptorEx и USBD_ParseDescriptor. Пример кода см. в примере функции с именем RetrieveStreamInfoFromEndpointDesc в перечислении USB-каналов.

    Чтобы определить максимальное количество потоков, выберите меньшее из двух значений, поддерживаемых контроллером узла и конечной точкой.

  4. Выделите массив структур USBD_STREAM_INFORMATION с n элементами, где n — это количество потоков для открытия. Драйвер клиента отвечает за освобождение этого массива после завершения работы драйвера с использованием потоков.

  5. Выделите URB для запроса open-streams, вызвав метод WdfUsbTargetDeviceCreateUrb . Если вызов завершится успешно, метод извлекает объект памяти WDF и адрес структуры URB , выделенной стеком USB-драйверов.

    Драйверы WDM: Вызовите процедуру USBD_UrbAllocate .

  6. Отформатируйте URB для запроса на открытие потока. URB использует структуру _URB_OPEN_STATIC_STREAMS для определения запроса. Чтобы отформатировать URB, вам потребуется:

    • Дескриптор USBD-канала к конечной точке. Если у вас есть объект канала WDF, можно получить дескриптор канала USBD, вызвав метод WdfUsbTargetPipeWdmGetPipeHandle .
    • Массив потоков (созданный на шаге 4)
    • Указатель на структуру URB (созданную на шаге 5).

    Чтобы отформатировать URB, вызовите UsbBuildOpenStaticStreamsRequest и передайте необходимые сведения в качестве значений параметров. Убедитесь, что количество потоков, указанных в UsbBuildOpenStaticStreamsRequest , не превышает максимальное количество поддерживаемых потоков.

  7. Отправьте URB в качестве объекта запроса WDF, вызвав метод WdfRequestSend . Чтобы отправить запрос синхронно, вызовите вместо этого метод WdfUsbTargetDeviceSendUrbSynchronously .

    Драйверы WDM: Свяжите URB с IRP и отправьте IRP в стек USB-драйверов. Для получения дополнительной информации см. раздел Как отправить URB.

  8. После завершения запроса проверьте состояние запроса.

    Если стек USB-драйверов не выполняет запрос, статус URB содержит соответствующий код ошибки. Некоторые распространенные условия сбоя описаны в разделе "Примечания".

Если состояние запроса (IRP или объекта запроса WDF) указывает USBD_STATUS_SUCCESS, запрос был успешно выполнен. Проверьте массив структур USBD_STREAM_INFORMATION, полученных после завершения. Массив заполняется сведениями о запрошенных потоках. Стек USB-драйверов заполняет каждую структуру массива сведениями о потоке, такими как дескрипторы (полученные как USBD_PIPE_HANDLE), идентификаторы потока и максимальный размер передачи. Потоки теперь открыты для передачи данных.

Для запроса open-streams необходимо выделить URB и массив. Драйвер клиента должен освободить URB, вызвав метод WdfObjectDelete в связанном объекте памяти WDF после завершения запроса открытых потоков. Если драйвер отправил запрос синхронно путем вызова WdfUsbTargetDeviceSendUrbSynchronous, он должен освободить объект памяти WDF после возврата метода. Если драйвер клиента отправил запрос асинхронно путем вызова WdfRequestSend, драйвер должен освободить объект памяти WDF в подпрограмме завершения, реализуемой драйвером, связанной с запросом.

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

Как передать данные в определенный поток

Чтобы отправить запрос на передачу данных в определенный поток, вам потребуется объект запроса WDF. Как правило, драйвер клиента не требуется для выделения объекта запроса WDF. Когда диспетчер операций ввода-вывода получает запрос от приложения, диспетчер операций ввода-вывода создает IRP для запроса. Этот IRP перехватывается фреймворком. Затем платформа выделяет объект запроса WDF для представления IRP. После этого платформа передает объект запроса WDF драйверу клиента. Затем клиентский драйвер может связать объект запроса с URB для передачи данных и отправить его в стек драйверов USB.

Если драйвер клиента не получает объект запроса WDF из платформы и хочет отправить запрос асинхронно, драйвер должен выделить объект запроса WDF, вызвав метод WdfRequestCreate . Отформатируйте новый объект, вызвав WdfUsbTargetPipeFormatRequestForUrb и отправив запрос, вызвав WdfRequestSend.

В синхронных случаях передача объекта запроса WDF необязательна.

Для передачи данных в потоки необходимо использовать URI. URB должен быть отформатирован путем вызова WdfUsbTargetPipeFormatRequestForUrb.

Для потоков не поддерживаются следующие методы WDF:

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

  1. Выделите URB путем вызова WdfUsbTargetDeviceCreateUrb. Этот метод выделяет объект памяти WDF, содержащий только что выделенный URB. Драйвер клиента может выделить URB для каждого запроса ввода-вывода или выделить URB и повторно использовать его для того же типа запроса.

  2. Отформатируйте URB для массовой передачи путем вызова UsbBuildInterruptOrBulkTransferRequest. В параметре PipeHandle укажите дескриптор потока. Дескриптор потока был получен в предыдущем запросе, описанном в разделе "Открытие статических потоков ".

  3. Отформатируйте объект запроса WDF, вызвав метод WdfUsbTargetPipeFormatRequestForUrb . В вызове укажите объект памяти WDF, в котором содержится URB для передачи данных. Объект памяти был выделен на шаге 1.

  4. Отправьте URB как запрос WDF путем вызова WdfRequestSend или WdfUsbTargetPipeSendUrbSynchronously. При вызове WdfRequestSend необходимо указать подпрограмму завершения, вызвав WdfRequestSetCompletionRoutine , чтобы драйвер клиента смог получать уведомления после завершения асинхронной операции. Необходимо освободить URB передачи данных в подпрограмме завершения.

Драйверы WDM: Выделите URB путем вызова USBD_UrbAllocate и форматирования его для массовой передачи (см. _URB_BULK_OR_INTERRUPT_TRANSFER). Чтобы отформатировать URB, можно вызвать USBBuildInterruptOrBulkTransferRequest или вручную отформатировать структуру URB. Укажите дескриптор для потока в члене URB UrbBulkOrInterruptTransfer.PipeHandle.

Закрытие статических потоков

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

  1. Выделите структуру URB путем вызова WdfUsbTargetDeviceCreateUrb.

  2. Отформатируйте URB для запроса закрытия потоков. Элемент UrbPipeRequest структуры URB является структурой _URB_PIPE_REQUEST. Заполните его элементы следующим образом:

    • Элемент Hdr_URB_PIPE_REQUEST должен быть URB_FUNCTION_CLOSE_STATIC_STREAMS
    • Элемент PipeHandle должен быть дескриптором конечной точки, содержащей открытые потоки, используемые.
  3. Отправьте URB как запрос WDF путем вызова WdfRequestSend или WdfUsbTargetDeviceSendUrbSynchronously.

Запрос закрытия закрывает все потоки, которые ранее были открыты драйвером клиента. Драйвер клиента не может использовать запрос для закрытия определенных потоков в конечной точке.

Лучшие практики по отправке запроса статических потоков

Стек драйверов USB выполняет проверку полученного URB. Чтобы избежать ошибок проверки:

  • Не отправляйте запрос open-stream или close-stream в конечную точку, которая не поддерживает потоки. Вызовите WdfUsbTargetDeviceQueryUsbCapability (для драйверов WDM, USBD_QueryUsbCapability), чтобы определить поддержку статических потоков и отправлять только запросы потоков, если конечная точка поддерживает их.
  • Не запрашивайте число (открытых потоков), превышающее максимальное количество поддерживаемых потоков или отправляя запрос, не указывая количество потоков. Определите количество потоков на основе количества потоков, поддерживаемых стеком USB-драйверов и конечной точкой устройства.
  • Не отправляйте запрос открытого потока в конечную точку, которая уже имеет открытые потоки.
  • Не отправляйте запрос закрытия потока в конечную точку, в которой нет открытых потоков.
  • После открытия статических потоков для конечной точки не отправляйте запросы ввода-вывода, используя дескриптор канала конечной точки, полученный с помощью запросов на выбор конфигурации или интерфейса. Это верно, даже если статические потоки были закрыты.

Сброс и прерывание операций канала передачи данных

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

Для передачи потоков запросы abort-pipe и reset-pipe не поддерживаются для отдельных потоков, связанных с массовой конечной точкой. Если передача завершается сбоем в определенном канале потока, контроллер узла останавливает передачу всех остальных каналов (для других потоков). Чтобы восстановить состояние ошибки, драйвер клиента должен вручную отменить передачу в каждый поток. Затем драйвер клиента должен отправить запрос на сброс канала, используя дескриптор канала, к массовой конечной точке. Для этого запроса драйвер клиента должен указать дескриптор канала в конечную точку в структуре _URB_PIPE_REQUEST и задать для функции URB (Hdr.Function) значение URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL.

Полный пример

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

NTSTATUS
    OpenStreams (
    _In_ WDFDEVICE Device,
    _In_ WDFUSBPIPE Pipe)
{
    NTSTATUS status;
    PDEVICE_CONTEXT deviceContext;
    PPIPE_CONTEXT pipeContext;
    USHORT cStreams = 0;
    USBD_PIPE_HANDLE usbdPipeHandle;
    WDFMEMORY urbMemory = NULL;
    PURB      urb = NULL;

    PAGED_CODE();

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

    if (deviceContext->MaxStreamsController == 0)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Static streams are not supported.");

        status = STATUS_NOT_SUPPORTED;
        goto Exit;
    }

    // If static streams are not supported, number of streams supported is zero.

    if (pipeContext->MaxStreamsSupported == 0)
    {
        status = STATUS_DEVICE_CONFIGURATION_ERROR;

        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Static streams are not supported by the endpoint.");

        goto Exit;
    }

    // Determine the number of streams to open.
    // Compare the number of streams supported by the endpoint with the
    // number of streams supported by the host controller, and choose the
    // lesser of the two values. The deviceContext->MaxStreams value was
    // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
    // that determined whether or not static streams is supported and
    // retrieved the maximum number of streams supported by the
    // host controller. The device context stores the values for IN and OUT
    // endpoints.

    // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
    // The number of elements in the array is the number of streams to open.
    // The code snippet stores the array in its device context.

    cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);

    // Allocate an array of streams associated with the IN bulk endpoint
    // This array is released in CloseStreams.

    pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
        NonPagedPool,
        sizeof (USBD_STREAM_INFORMATION) * cStreams,
        USBCLIENT_TAG);

    if (pipeContext->StreamInfo == NULL)
    {
        status = STATUS_INSUFFICIENT_RESOURCES;

        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not allocate stream information array.");

        goto Exit;
    }

    RtlZeroMemory (pipeContext->StreamInfo,
        sizeof (USBD_STREAM_INFORMATION) * cStreams);

    // Get USBD pipe handle from the WDF target pipe object. The client driver received the
    // endpoint pipe handles during device configuration.

    usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);

    // Allocate an URB for the open streams request.
    // WdfUsbTargetDeviceCreateUrb returns the address of the
    // newly allocated URB and the WDFMemory object that
    // contains the URB.

    status = WdfUsbTargetDeviceCreateUrb (
        deviceContext->UsbDevice,
        NULL,
        &urbMemory,
        &urb);

    if (status != STATUS_SUCCESS)
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not allocate URB for an open-streams request.");

        goto Exit;
    }

    // Format the URB for the open-streams request.
    // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
    // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.

    UsbBuildOpenStaticStreamsRequest (
        urb,
        usbdPipeHandle,
        (USHORT)cStreams,
        pipeContext->StreamInfo);

    // Send the request synchronously.
    // Upon completion, the USB driver stack populates the array of with handles to streams.

    status = WdfUsbTargetPipeSendUrbSynchronously (
        Pipe,
        NULL,
        NULL,
        urb);

    if (status != STATUS_SUCCESS)
    {
        goto Exit;
    }

Exit:
    if (urbMemory)
    {
        WdfObjectDelete (urbMemory);
    }

    return status;
}