Отправка запросов на массовую передачу по USB

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

Сведения о массовых конечных точках

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

Ниже приведены основные возможности массовой конечной точки.

  • Массовые конечные точки являются необязательными. Они поддерживаются USB-устройством, которое хочет передавать большие объемы данных. Например, передача файлов на устройство флэш-памяти, передачи данных на принтер или сканер.
  • Устройства USB full speed, high speed и SuperSpeed поддерживают массовые конечные точки. Низкоскоростные устройства не поддерживают массовые конечные точки.
  • Конечная точка является однонаправленной, и данные могут передаваться в направлении IN или OUT. Конечная точка bulk IN используется для чтения данных с устройства на узел, а конечная точка bulk OUT используется для отправки данных с узла на устройство.
  • Конечная точка имеет биты CRC для проверка ошибок и, таким образом, обеспечивает целостность данных. При возникновении ошибок CRC данные повторно передаются автоматически.
  • Массовая конечная точка SuperSpeed может поддерживать потоки. Потоки позволяют узлу отправлять передачи в отдельные потоковые каналы.
  • Максимальный размер пакета для массовой конечной точки зависит от скорости шины устройства. Для полной, высокой скорости и SuperSpeed; максимальный размер пакета составляет 64, 512 и 1024 байта соответственно.

Массовые транзакции

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

Способ взаимодействия узла и устройства в шине зависит от скорости подключения устройства. В этом разделе описываются некоторые примеры высокоскоростных и массовых передач SuperSpeed, которые показывают обмен данными между узлом и устройством.

Структуру транзакций и пакетов можно просмотреть с помощью любого АНАЛИЗАТОРа USB, например Анализаторы протоколов Beagle, Ellisys, LeCroy. Устройство анализатора показывает, как данные отправляются на USB-устройство или принимаются с его помощью по сети. В этом примере давайте рассмотрим некоторые трассировки, захваченные USB-анализатором LeCroy. Этот пример предназначен только для получения сведений. Это не является подтверждением корпорации Майкрософт.

Пример транзакции Bulk OUT

Эта трассировка анализатора показывает пример массовой транзакции OUT с высокой скоростью.

Снимок экрана, на котором показана трассировка примера транзакции анализатора массовых операций out.

В предыдущей трассировке узел инициирует массовую передачу в высокоскоростную конечную точку массовых операций, отправляя пакет маркера с piD, для которых задано значение OUT (out token). Пакет содержит адрес устройства и целевой конечной точки. После пакета OUT узел отправляет пакет данных, содержащий полезные данные для массовых операций. Если конечная точка принимает входящие данные, она отправляет пакет ACK. В этом примере видно, что узел отправил 31 байт на адрес устройства: 1; адрес конечной точки: 2.

Если конечная точка занята во время поступления пакета данных и не может получить данные, устройство может отправить пакет NAK. В этом случае узел начинает отправлять пакеты PING на устройство. Устройство отвечает пакетами NAK, если устройство не готово к получению данных. Когда устройство будет готово, оно отвечает пакетом ACK. Затем узел может возобновить передачу out.

Эта трассировка анализатора показывает пример транзакции SuperSpeed bulk OUT.

Снимок экрана, на котором показана трассировка примера транзакции данных SuperSpeed bulk OUT.

В приведенной выше трассировке узел инициирует транзакцию OUT к массовой конечной точке SuperSpeed, отправляя пакет данных. Пакет данных содержит массовые полезные данные, адреса устройств и конечных точек. В этом примере видно, что узел отправил 31 байт на адрес устройства:4; адрес конечной точки: 2.

Устройство получает и подтверждает пакет данных и отправляет пакет ACK обратно на узел. Если конечная точка занята во время поступления пакета данных и не может получить данные, устройство может отправить пакет NRDY. В отличие от высокой скорости, после получения пакета NRDY узел не выполняет повторный опрос устройства. Вместо этого узел ожидает ERDY от устройства. Когда устройство будет готово, оно отправляет пакет ERDY, и узел может отправить данные в конечную точку.

Пример массовой транзакции IN

Эта трассировка анализатора показывает пример массовой транзакции IN с высокой скоростью.

Снимок экрана: трассировка примера массовой транзакции данных IN.

В предыдущей трассировке узел инициирует транзакцию, отправляя пакет маркера с идентификатором PID, равным IN (in token). Затем устройство отправляет пакет данных с массовыми полезными данными. Если конечная точка не имеет данных для отправки или еще не готова к отправке данных, устройство может отправить пакет подтверждения NAK. Узел повторяет передачу IN, пока не получит пакет ACK от устройства. Этот пакет ACK подразумевает, что устройство приняло данные.

Эта трассировка анализатора показывает пример транзакции SuperSpeed bulk IN.

трассировка примера транзакции данных.

Чтобы инициировать массовую передачу in из конечной точки SuperSpeed, узел запускает массовую транзакцию, отправляя пакет ACK. Спецификация USB версии 3.0 оптимизирует эту начальную часть передачи путем объединения пакетов ACK и IN в один пакет ACK. Вместо токена IN для SuperSpeed узел отправляет токен ACK для запуска массовой передачи. Устройство отвечает пакетом данных. Затем узел подтверждает пакет данных, отправляя пакет ACK. Если конечная точка занята и ей не удалось отправить данные, устройство может отправлять состояние NRDY. В этом случае узел ожидает, пока не получит пакет ERDY от устройства.

Задачи драйвера USB-клиента для массовой передачи

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

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

Для драйвера KMDF запрос описывается в объекте запроса платформы (см. справочник по объекту запроса WDF). Драйвер клиента вызывает методы объекта запроса, указывая дескриптор WDFREQUEST для отправки запроса в стек драйверов USB. Если драйвер клиента отправляет массовую передачу в ответ на запрос от приложения или другого драйвера, платформа создает объект запроса и доставляет запрос драйверу клиента с помощью объекта очереди платформы. В этом случае драйвер клиента может использовать этот запрос для отправки массовой передачи. Если клиентский драйвер инициировал запрос, драйвер может выбрать выделение собственного объекта запроса.

Если приложение или другой драйвер отправляет или запрашивает данные, буфер передачи передается драйверу платформой. Кроме того, драйвер клиента может выделить буфер передачи и создать объект запроса, если драйвер инициирует передачу самостоятельно.

Ниже приведены main задачи для драйвера клиента:

  1. Получите буфер передачи.
  2. Получение, форматирование и отправка объекта запроса платформы в стек драйверов USB.
  3. Реализуйте подпрограмму завершения, чтобы получать уведомления о завершении запроса стеком драйверов USB.

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

Для чтения данных с устройства драйвер клиента может использовать предоставленный платформой объект непрерывного чтения. Дополнительные сведения см. в статье Использование непрерывного считывания данных из USB-канала.

Пример запроса на массовую передачу

Рассмотрим пример сценария, в котором приложению требуется считывать или записывать данные на устройство. Приложение вызывает API-интерфейсы Windows для отправки таких запросов. В этом примере приложение открывает дескриптор устройства с помощью GUID интерфейса устройства, опубликованного драйвером в режиме ядра. Затем приложение вызывает ReadFile или WriteFile , чтобы инициировать запрос на чтение или запись. В этом вызове приложение также указывает буфер, содержащий данные для чтения или записи, а также длину этого буфера.

Диспетчер ввода-вывода получает запрос, создает пакет запроса ввода-вывода (IRP) и перенаправит его драйверу клиента.

Платформа перехватывает запрос, создает объект запроса платформы и добавляет его в объект очереди платформы. Затем платформа уведомляет драйвер клиента о том, что новый запрос ожидает обработки. Это уведомление выполняется путем вызова подпрограмм обратного вызова очереди драйвера для EvtIoRead или EvtIoWrite.

Когда платформа доставляет запрос драйверу клиента, она получает следующие параметры:

  • WDFQUEUE дескриптор объекта очереди платформы, который содержит запрос.
  • Дескриптор WDFREQUEST для объекта запроса платформы, который содержит сведения об этом запросе.
  • Длина передачи, то есть количество байтов для чтения или записи.

В реализации Драйвера клиента EvtIoRead или EvtIoWrite драйвер проверяет параметры запроса и при необходимости может выполнять проверки.

Если вы используете потоки массовой конечной точки SuperSpeed, запрос отправляется в URB, так как KMDF не поддерживает потоки внутренне. Сведения об отправке запроса на передачу в потоки массовой конечной точки см. в статье Открытие и закрытие статических потоков в массовой конечной точке USB.

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

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

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

  • Драйвер клиента должен создать объект целевого устройства ПЛАТФОРМы USB и получить дескриптор WDFUSBDEVICE, вызвав метод WdfUsbTargetDeviceCreateWithParameters .

    Если вы используете шаблоны USB, которые предоставляются в Microsoft Visual Studio Professional 2012, код шаблона выполняет эти задачи. Код шаблона получает дескриптор целевого объекта устройства и сохраняет его в контексте устройства. Дополнительные сведения см. в разделе "Исходный код устройства" статьи Общие сведения о структуре кода драйвера USB-клиента (KMDF).

  • Дескриптор WDFREQUEST для объекта запроса платформы, который содержит сведения об этом запросе.

  • Число байтов для чтения или записи.

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

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

Шаг 1. Получение буфера передачи

Буфер передачи или MDL буфера передачи содержит данные для отправки или получения. В этом разделе предполагается, что вы отправляете или получаете данные в буфере передачи. Буфер передачи описывается в объекте памяти WDF (см. справочник по объекту памяти WDF). Чтобы получить объект памяти, связанный с буфером передачи, вызовите один из следующих методов:

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

Шаг 2. Форматирование и отправка объекта запроса платформы в стек драйверов USB

Запрос на передачу можно отправить асинхронно или синхронно.

Асинхронные методы:

Методы в этом списке форматит запрос. Если вы отправляете запрос асинхронно, установите указатель на процедуру завершения, реализованную драйвером, вызвав метод WdfRequestSetCompletionRoutine (описанный в следующем шаге). Чтобы отправить запрос, вызовите метод WdfRequestSend .

Если вы отправляете запрос синхронно, вызовите следующие методы:

Примеры кода см. в разделе Примеры справочных разделов по этим методам.

Шаг 3. Реализация процедуры завершения для запроса

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

  • Дескриптор WDFREQUEST для объекта запроса.
  • WDFIOTARGET дескриптор целевого объекта ввода-вывода для запроса.
  • Указатель на структуру WDF_REQUEST_COMPLETION_PARAMS , содержащую сведения о завершении. Сведения, относящиеся к USB, содержатся в элементе CompletionParams-Parameters.Usb>.
  • WDFCONTEXT дескриптор контекста, указанного драйвером в вызове WdfRequestSetCompletionRoutine.

В подпрограмме завершения выполните следующие задачи:

  • Проверьте состояние запроса, получив значение CompletionParams-IoStatus.Status>.

  • Проверьте состояние USBD, установленное стеком драйверов USB.

  • В случае ошибок канала выполните операции восстановления ошибок. Дополнительные сведения см. в статье Восстановление после ошибок USB-канала.

  • Проверьте количество переданных байтов.

    Массовая передача завершается, когда запрошенное количество байтов было передано на устройство или с него. При отправке буфера запросов путем вызова метода KMDF проверка значение, полученное в элементах CompletionParams-Parameters.Usb.Completion-Parameters.PipeWrite.Length>> или CompletionParams-Parameters.Usb.Completion-Parameters.PipeRead.Length>>.

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

  • Если было передано общее количество байтов, выполните запрос. Если возникла ошибка, завершите запрос с возвращенным кодом ошибки. Выполните запрос, вызвав метод WdfRequestComplete . Если вы хотите задать такие сведения, как количество переданных байтов, вызовите WdfRequestCompleteWithInformation.

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

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

/*++

Routine Description:

This routine sends a bulk write request to the
USB driver stack. The request is sent asynchronously and
the driver gets notified through a completion routine.

Arguments:

Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.


Return Value:

VOID

--*/


VOID Fx3EvtIoWrite(
    IN WDFQUEUE  Queue,
    IN WDFREQUEST  Request,
    IN size_t  Length
    )
{
    NTSTATUS  status;
    WDFUSBPIPE  pipe;
    WDFMEMORY  reqMemory;
    PDEVICE_CONTEXT  pDeviceContext;

    pDeviceContext = GetDeviceContext(WdfIoQueueGetDevice(Queue));

    pipe = pDeviceContext->BulkWritePipe;

    status = WdfRequestRetrieveInputMemory(
                                           Request,
                                           &reqMemory
                                           );
    if (!NT_SUCCESS(status))
    {
        goto Exit;
    }

    status = WdfUsbTargetPipeFormatRequestForWrite(
                                                   pipe,
                                                   Request,
                                                   reqMemory,
                                                   NULL
                                                   );
    if (!NT_SUCCESS(status))
       {
        goto Exit;
    }

    WdfRequestSetCompletionRoutine(
                                   Request,
                                   BulkWriteComplete,
                                   pipe
                                   );

    if (WdfRequestSend( Request,
                        WdfUsbTargetPipeGetIoTarget(pipe),
                        WDF_NO_SEND_OPTIONS) == FALSE)
       {
        status = WdfRequestGetStatus(Request);
        goto Exit;
    }

Exit:
    if (!NT_SUCCESS(status)) {
        WdfRequestCompleteWithInformation(
                                          Request,
                                          status,
                                          0
                                          );
    }
    return;
}

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

/*++

Routine Description:

This completion routine is invoked by the framework when
the USB drive stack completes the previously sent
bulk write request. The client driver completes the
the request if the total number of bytes were transferred
to the device.
In case of failure it queues a work item to start the
error recovery by resetting the target pipe.

Arguments:

Queue - Handle to a framework queue object.
Request - Handle to the framework request object.
Length - Number of bytes to transfer.
Pipe - Handle to the pipe that is the target for this request.

Return Value:

VOID

--*/

VOID BulkWriteComplete(
    _In_ WDFREQUEST                  Request,
    _In_ WDFIOTARGET                 Target,
    PWDF_REQUEST_COMPLETION_PARAMS   CompletionParams,
    _In_ WDFCONTEXT                  Context
    )
{

    PDEVICE_CONTEXT deviceContext;

    size_t          bytesTransferred=0;

    NTSTATUS        status;


    UNREFERENCED_PARAMETER (Target);
    UNREFERENCED_PARAMETER (Context);


    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
        "In completion routine for Bulk transfer.\n"));

    // Get the device context. This is the context structure that
    // the client driver provided when it sent the request.

    deviceContext = (PDEVICE_CONTEXT)Context;

    // Get the status of the request
    status = CompletionParams->IoStatus.Status;
    if (!NT_SUCCESS (status))
    {
        // Get the USBD status code for more information about the error condition.
        status = CompletionParams->Parameters.Usb.Completion->UsbdStatus;

        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
            "Bulk transfer failed. 0x%x\n",
            status));

        // Queue a work item to start the reset-operation on the pipe
        // Not shown.

        goto Exit;
    }

    // Get the actual number of bytes transferred.
    bytesTransferred =
            CompletionParams->Parameters.Usb.Completion->Parameters.PipeWrite.Length;

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
            "Bulk transfer completed. Transferred %d bytes. \n",
            bytesTransferred));

Exit:

    // Complete the request and update the request with
    // information about the status code and number of bytes transferred.

    WdfRequestCompleteWithInformation(Request, status, bytesTransferred);

    return;
}