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

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

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

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

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

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

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

Платформа драйвера режима ядра (KMDF) не поддерживает статические потоки по своей сути. Драйвер клиента должен отправлять универсальные коды ресурса (WDM) в стиле WDM, которые открывают и закрывают потоки. В этой статье описывается форматирование и отправка этих URI. Драйвер UMDF-client не может использовать возможность статических потоков.

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

Предварительные требования

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

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

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

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

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

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

    Драйверы WDM: Получите дескриптор канала USBD, отправив запрос select-configuration или select-interface. Дополнительные сведения см. в статье Выбор конфигурации для 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 для запроса open-stream. URB использует структуру _URB_OPEN_STATIC_STREAMS для определения запроса. Чтобы отформатировать URB, вам потребуется:

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

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

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

    Драйверы WDM: Свяжите URB с IRP и отправьте его в стек драйверов USB. Дополнительные сведения см. в статье Отправка URB.

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

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

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

Для запроса open-streams необходимо выделить URB и массив. Драйвер клиента должен освободить URB, вызвав метод WdfObjectDelete для связанного объекта памяти WDF после завершения запроса открытых потоков. Если драйвер отправил запрос синхронно, вызвав WdfUsbTargetDeviceSendUrbSynchronously, он должен освободить объект памяти 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 вручную. Укажите дескриптор потока в элементе UrbBulkOrInterruptTransfer.PipeHandle 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. Чтобы избежать ошибок проверки:

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

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

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

Для потоковой передачи запросы прервать и сбросить канал не поддерживаются для отдельных потоков, связанных с конечной точкой массовых операций. Если передача данных завершается сбоем по определенному каналу потока, контроллер узла останавливает передачу данных по всем остальным каналам (для других потоков). Чтобы восстановить состояние ошибки, драйвер клиента должен вручную отменить передачу в каждый поток. Затем драйвер клиента должен отправить запрос канала сброса с помощью дескриптора канала в массовую конечную точку. Для этого запроса драйвер клиента должен указать дескриптор канала конечной точки в структуре _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;
}