Обмен технологиями

Разработка драйвера USB-устройства Windows для обработки статических потоков пакетной передачи

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

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

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

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

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

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

Платформа драйверов режима ядра (KMDF) изначально не поддерживает статические потоки. Клиентские драйверы должны использовать модель драйверов Windows (WDM) для открытия и закрытия потоков. Драйверы клиента User-mode Driver Framework (UMDF) не могут использовать функцию статической потоковой передачи.

Следующее может содержать некоторые комментарии с пометкой «Драйверы WDM». Эти инструкции описывают процедуры для драйвера USB-клиента на основе WDM, который хочет отправлять запросы потоковой передачи.

предпосылки

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

1. Вызовите метод WdfUsbTargetDeviceCreateWithParameters. Для метода требуется версия клиентского протокола USBD_CLIENT_CONTRACT_VERSION_602. Указывая эту версию, клиентский драйвер должен соответствовать набору правил.

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

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

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

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

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

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

Драйвер WDM: вызывает процедуру USBD_QueryUsbCapability. Обычно драйвер запрашивает функцию, которая будет использоваться в подпрограмме загрузочного устройства драйвера (IRP_MN_START_DEVICE).

Предоставьте следующую информацию:

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

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

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

Например, если 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 (получает WdfUsbTargetDeviceQueryUsbCapabilityUSBD_QueryUsbCapability) в выходном буфере, предоставленном вызывающей стороной. Стек драйверов USB, предоставленный Microsoft, поддерживает до 255 потоков. WdfUsbTargetDeviceQueryUsbCapability учитывает это ограничение при расчете количества потоков. Метод никогда не возвращает значение больше 255.
  • Максимальное количество потоков, поддерживаемое конечной точкой на устройстве. Чтобы получить этот номер, проверьте сопутствующий дескриптор конечной точки (в Usbspec.h) для USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR. Чтобы получить сопутствующий дескриптор конечной точки, необходимо проанализировать дескриптор конфигурации. Чтобы получить дескриптор конфигурации, драйвер клиента должен вызвать метод WdfUsbTargetDeviceRetrieveConfigDescriptor. Необходимо использовать вспомогательные процедуры USBD_ParseConfigurationDescriptorEx и USBD_ParseDescriptor.

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

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

5. Выделите URB для запроса на открытие потока, вызвав метод 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. Чтобы отправить запрос синхронно, вместо этого вызовите метод WdfUsbTargetDeviceSendUrbSynchronous.

Драйвер WDM: связывает URB с IRP и отправляет IRP в стек драйверов USB.

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

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

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

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

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

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

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

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

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

  • WdfUsbTargetPipeFormatRequestForRead
  • WdfUsbTargetPipeFormatRequestForWrite
  • WdfUsbTargetPipeReadSynchronously
  • WdfUsbTargetPipeWriteSynchronously

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

  1. Выделите URB, вызвав WdfUsbTargetDeviceCreateUrb. Этот метод выделяет объект памяти WDF, содержащий вновь выделенный URB. Драйверы клиентов могут выделить URB для каждого запроса ввода-вывода или выделить URB и использовать его для запросов одного и того же типа.
  2. Отформатируйте URB для массовой передачи, вызвав UsbBuildInterruptOrBulkTransferRequest. В параметре PipeHandle укажите дескриптор потока. Дескриптор потока получается в предыдущем запросе, как описано в разделе «Как открыть статический поток».
  3. Отформатируйте объект запроса WDF, вызвав метод WdfUsbTargetPipeFormatRequestForUrb. В вызове укажите объект памяти WDF, содержащий URB передачи данных. Объект памяти был выделен на шаге 1.
  4. Отправьте URB как запрос WDF, вызвав WdfRequestSend или WdfUsbTargetPipeSendUrbSynchronous. Если вы вызываете WdfRequestSend, вы должны указать процедуру завершения, вызвав WdfRequestSetCompletionRoutine, чтобы драйвер клиента мог быть уведомлен о завершении асинхронной операции. URB передачи данных должен быть освобожден в процедуре завершения.

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

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

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

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

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

  • Обязательный член Hdr URB_FUNCTION_CLOSE_STATIC_STREAMS_URB_PIPE_REQUEST
  • Член PipeHandle должен быть дескриптором, содержащим конечную точку, используемую для открытия потока.

3. Отправьте URB как запрос WDF, вызвав WdfRequestSend или WdfUsbTargetDeviceSendUrbSynchronous.

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

Рекомендации по отправке запросов статической потоковой передачи

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

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

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

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

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

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

  1. NTSTATUS
  2. OpenStreams (
  3. _In_ WDFDEVICE Device,
  4. _In_ WDFUSBPIPE Pipe)
  5. {
  6. NTSTATUS status;
  7. PDEVICE_CONTEXT deviceContext;
  8. PPIPE_CONTEXT pipeContext;
  9. USHORT cStreams = 0;
  10. USBD_PIPE_HANDLE usbdPipeHandle;
  11. WDFMEMORY urbMemory = NULL;
  12. PURB urb = NULL;
  13. PAGED_CODE();
  14. deviceContext =GetDeviceContext(Device);
  15. pipeContext = GetPipeContext (Pipe);
  16. if (deviceContext->MaxStreamsController == 0)
  17. {
  18. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  19. "%!FUNC! Static streams are not supported.");
  20. status = STATUS_NOT_SUPPORTED;
  21. goto Exit;
  22. }
  23. // If static streams are not supported, number of streams supported is zero.
  24. if (pipeContext->MaxStreamsSupported == 0)
  25. {
  26. status = STATUS_DEVICE_CONFIGURATION_ERROR;
  27. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  28. "%!FUNC! Static streams are not supported by the endpoint.");
  29. goto Exit;
  30. }
  31. // Determine the number of streams to open.
  32. // Compare the number of streams supported by the endpoint with the
  33. // number of streams supported by the host controller, and choose the
  34. // lesser of the two values. The deviceContext->MaxStreams value was
  35. // obtained in a previous call to WdfUsbTargetDeviceQueryUsbCapability
  36. // that determined whether or not static streams is supported and
  37. // retrieved the maximum number of streams supported by the
  38. // host controller. The device context stores the values for IN and OUT
  39. // endpoints.
  40. // Allocate an array of USBD_STREAM_INFORMATION structures to store handles to streams.
  41. // The number of elements in the array is the number of streams to open.
  42. // The code snippet stores the array in its device context.
  43. cStreams = min(deviceContext->MaxStreamsController, pipeContext->MaxStreamsSupported);
  44. // Allocate an array of streams associated with the IN bulk endpoint
  45. // This array is released in CloseStreams.
  46. pipeContext->StreamInfo = (PUSBD_STREAM_INFORMATION) ExAllocatePoolWithTag (
  47. NonPagedPool,
  48. sizeof (USBD_STREAM_INFORMATION) * cStreams,
  49. USBCLIENT_TAG);
  50. if (pipeContext->StreamInfo == NULL)
  51. {
  52. status = STATUS_INSUFFICIENT_RESOURCES;
  53. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  54. "%!FUNC! Could not allocate stream information array.");
  55. goto Exit;
  56. }
  57. RtlZeroMemory (pipeContext->StreamInfo,
  58. sizeof (USBD_STREAM_INFORMATION) * cStreams);
  59. // Get USBD pipe handle from the WDF target pipe object. The client driver received the
  60. // endpoint pipe handles during device configuration.
  61. usbdPipeHandle = WdfUsbTargetPipeWdmGetPipeHandle (Pipe);
  62. // Allocate an URB for the open streams request.
  63. // WdfUsbTargetDeviceCreateUrb returns the address of the
  64. // newly allocated URB and the WDFMemory object that
  65. // contains the URB.
  66. status = WdfUsbTargetDeviceCreateUrb (
  67. deviceContext->UsbDevice,
  68. NULL,
  69. &urbMemory,
  70. &urb);
  71. if (status != STATUS_SUCCESS)
  72. {
  73. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
  74. "%!FUNC! Could not allocate URB for an open-streams request.");
  75. goto Exit;
  76. }
  77. // Format the URB for the open-streams request.
  78. // The UsbBuildOpenStaticStreamsRequest inline function formats the URB by specifying the
  79. // pipe handle to the entire bulk endpoint, number of streams to open, and the array of stream structures.
  80. UsbBuildOpenStaticStreamsRequest (
  81. urb,
  82. usbdPipeHandle,
  83. (USHORT)cStreams,
  84. pipeContext->StreamInfo);
  85. // Send the request synchronously.
  86. // Upon completion, the USB driver stack populates the array of with handles to streams.
  87. status = WdfUsbTargetPipeSendUrbSynchronously (
  88. Pipe,
  89. NULL,
  90. NULL,
  91. urb);
  92. if (status != STATUS_SUCCESS)
  93. {
  94. goto Exit;
  95. }
  96. Exit:
  97. if (urbMemory)
  98. {
  99. WdfObjectDelete (urbMemory);
  100. }
  101. return status;
  102. }