Использование непрерывного средства чтения для чтения данных из USB-канала

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

Windows Driver Framework (WDF) предоставляет специализированный объект, называемый непрерывным средством чтения. Этот объект позволяет USB-драйверу клиента считывать данные из массовых конечных точек и прерывать их непрерывно, пока доступны данные. Чтобы использовать средство чтения, драйвер клиента должен иметь дескриптор для объекта канала USB-объекта, связанного с конечной точкой, из которой драйвер считывает данные. Конечная точка должна находиться в активной конфигурации. Вы можете сделать конфигурацию активной одним из двух способов: выбрав USB-конфигурацию или изменив альтернативный параметр в текущей конфигурации. Дополнительные сведения об этих операциях см. в разделе "Выбор конфигурации для USB-устройства " и "Как выбрать альтернативный параметр в USB-интерфейсе".

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

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

В этой статье используется следующее:

Перед началом работы

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

  • USB-устройство должно иметь конечную точку IN. Проверьте конфигурацию устройства в USBView. Usbview.exe — это приложение, которое позволяет просматривать все USB-контроллеры и USB-устройства, подключенные к ним. Как правило, USBView устанавливается в папку отладчиков в комплекте драйверов Windows (WDK).

  • Драйвер клиента должен создать объект целевого устройства 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 платформа выбирает первую конфигурацию и альтернативный параметр по умолчанию для каждого интерфейса в этой конфигурации.

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

Использование непрерывного чтения в драйвере клиента KMDF

Прежде чем начать использовать непрерывное средство чтения, необходимо настроить его, инициализировав структуру WDF_USB_CONTINUOUS_READER_CONFIG .

Настройка непрерывного чтения в драйвере клиента KMDF

  1. Инициализировать структуру WDF_USB_CONTINUOUS_READER_CONFIG путем вызова макроса WDF_USB_CONTINUOUS_READER_CONFIG_INIT.

  2. Укажите параметры конфигурации в структуре WDF_USB_CONTINUOUS_READER_CONFIG.

  3. Вызовите метод WdfUsbTargetPipeConfigContinuousReader.

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

    NTSTATUS FX3ConfigureContinuousReader(
        _In_ WDFDEVICE Device,
        _In_ WDFUSBPIPE Pipe)
    {
        NTSTATUS status;
        PDEVICE_CONTEXT                     pDeviceContext;
        WDF_USB_CONTINUOUS_READER_CONFIG    readerConfig;
        PPIPE_CONTEXT                       pipeContext;
    
        PAGED_CODE();
    
        pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
        pipeContext = GetPipeContext (Pipe);
    
        WDF_USB_CONTINUOUS_READER_CONFIG_INIT(
            &readerConfig,
            FX3EvtReadComplete,
            pDeviceContext,
            pipeContext->MaxPacketSize);
    
        readerConfig.EvtUsbTargetPipeReadersFailed=FX3EvtReadFailed;
    
        status = WdfUsbTargetPipeConfigContinuousReader(
            Pipe,
            &readerConfig);
    
        if (!NT_SUCCESS (status))
        {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                "%!FUNC! WdfUsbTargetPipeConfigContinuousReader failed 0x%x", status);
    
            goto Exit;
        }
    
    Exit:
        return status;
    }
    

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

В предыдущем примере драйвер клиента задает параметры конфигурации двумя способами. Сначала вызовите WDF_USB_CONTINUOUS_READER_CONFIG_INIT, а затем задав WDF_USB_CONTINUOUS_READER_CONFIG членов. Обратите внимание на параметры WDF_USB_CONTINUOUS_READER_CONFIG_INIT. Эти значения являются обязательными. В этом примере драйвер клиента указывает:

  • Указатель на подпрограмму завершения, реализуемую драйвером. Платформа вызывает эту подпрограмму при завершении запроса на чтение. В подпрограмме завершения драйвер может получить доступ к расположению памяти, содержащей данные, которые были прочитаны. Реализация подпрограммы завершения рассматривается на шаге 2.
  • Указатель на определяемый драйвером контекст.
  • Количество байтов, которые можно считывать с устройства в одной передаче. Драйвер клиента может получить эти сведения в структуре WDF_USB_PIPE_INFORMATION путем вызова метода WdfUsbInterfaceGetConfiguredPipe или WdfUsbTargetPipeGetInformation. Дополнительные сведения см. в разделе "Перечисление USB-каналов".

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

Помимо параметров конфигурации, указанных в WDF_USB_CONTINUOUS_READER_CONFIG_INIT, пример также задает подпрограмму сбоя в WDF_USB_CONTINUOUS_READER_CONFIG. Эта подпрограмма сбоя необязательна.

В дополнение к подпрограмме сбоя существуют другие члены в WDF_USB_CONTINUOUS_READER_CONFIG , которые драйвер клиента может использовать для указания макета буфера передачи. Например, рассмотрим сетевой драйвер, использующий непрерывное средство чтения для получения сетевых пакетов. Каждый пакет содержит данные заголовка, полезных данных и нижних колонтитулов. Чтобы описать пакет, драйвер должен сначала указать размер пакета в вызове WDF_USB_CONTINUOUS_READER_CONFIG_INIT. Затем драйвер должен указать длину верхнего и нижнего колонтитулов, задав элементы HeaderLength и TrailerLength WDF_USB_CONTINUOUS_READER_CONFIG. Платформа использует эти значения для вычисления смещения байтов на обеих сторонах полезных данных. Когда данные полезных данных считываются из конечной точки, платформа сохраняет эти данные в части буфера между смещениями.

Реализация подпрограммы завершения

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

В следующем примере кода показана реализация подпрограмм завершения.

EVT_WDF_USB_READER_COMPLETION_ROUTINE FX3EvtReadComplete;

VOID FX3EvtReadComplete(
    __in  WDFUSBPIPE Pipe,
    __in  WDFMEMORY Buffer,
    __in  size_t NumBytesTransferred,
    __in  WDFCONTEXT Context
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    PVOID  requestBuffer;

    pDeviceContext = (PDEVICE_CONTEXT)Context;

    if (NumBytesTransferred == 0)
    {
        return;
    }

    requestBuffer = WdfMemoryGetBuffer(Buffer, NULL);

    if (Pipe == pDeviceContext->InterruptPipe)
    {
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                                "Interrupt endpoint: %s.\n",
                                requestBuffer ));
    }

    return;
}

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

Реализация подпрограммы сбоя

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

В следующем примере кода показана реализация подпрограммы сбоя.

EVT_WDF_USB_READERS_FAILED FX3EvtReadFailed;

BOOLEAN
FX3EvtReadFailed(
    WDFUSBPIPE      Pipe,
    NTSTATUS        Status,
    USBD_STATUS     UsbdStatus
    )
{
    UNREFERENCED_PARAMETER(Status);

    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! ReadersFailedCallback failed NTSTATUS 0x%x, UsbdStatus 0x%x\n",
                    status,
                    UsbdStatus);

    return TRUE;
}

В предыдущем примере драйвер возвращает ЗНАЧЕНИЕ TRUE. Это значение указывает на платформу, которую необходимо сбросить канал, а затем перезапустить непрерывное средство чтения.

Кроме того, драйвер клиента может возвращать значение FALSE и предоставлять механизм восстановления ошибок, если состояние остановки происходит на канале. Например, драйвер может проверка состояние USBD и отправить запрос на сброс канала для очистки условия остановки.

Сведения об ошибке восстановления в каналах см. в разделе "Как восстановить из ошибок USB-канала".

Запуск и остановка непрерывного чтения

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

Непрерывное средство чтения не управляется платформой автоматически. Таким образом, драйвер клиента должен явно запустить или остановить целевой объект канала при изменении состояния питания устройства. Драйвер вызывает WdfIoTargetStart в реализации EvtDeviceD0Entry драйвера. Этот вызов гарантирует, что очередь отправляет запросы только в том случае, если устройство находится в рабочем состоянии. И наоборот, драйвер вызывает WdfIoTargetStop в реализации EvtDeviceD0Exit, чтобы очередь перестала доставлять запросы, когда устройство входит в более низкое состояние питания.

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

EVT_WDF_DEVICE_D0_ENTRY FX3EvtDeviceD0Entry;

NTSTATUS FX3EvtDeviceD0Entry(
    __in  WDFDEVICE Device,
    __in  WDF_POWER_DEVICE_STATE PreviousState
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    NTSTATUS status;

    PAGED_CODE();

    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
    status = WdfIoTargetStart (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe));

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not start interrupt pipe failed 0x%x", status);
    }
}

EVT_WDF_DEVICE_D0_EXIT FX3EvtDeviceD0Exit;

NTSTATUS FX3EvtDeviceD0Exit(
    __in  WDFDEVICE Device,
    __in  WDF_POWER_DEVICE_STATE TargetState
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    NTSTATUS status;
    PAGED_CODE();
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
    WdfIoTargetStop (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe), WdfIoTargetCancelSentIo));
}

В предыдущем примере показана реализация подпрограмм обратного вызова EvtDeviceD0Entry и EvtDeviceD0Exit. Параметр Action WdfIoTargetStop позволяет драйверу клиента решить действие для ожидающих запросов в очереди, когда устройство покидает рабочее состояние. В примере драйвер указывает WdfIoTargetCancelSentIo. Этот параметр указывает платформе отменять все ожидающие запросы в очереди. Кроме того, драйвер может указать платформе ждать завершения ожидающих запросов, прежде чем остановить целевой объект ввода-вывода или сохранить ожидающие запросы и возобновить работу при перезапуске целевого объекта ввода-вывода.

Использование непрерывного средства чтения в драйвере клиента UMDF

Перед началом работы с непрерывным средством чтения необходимо настроить средство чтения в реализации метода IPnpCallbackHardware::OnPrepareHardware. После получения указателя на интерфейс IWDFUsbTargetPipe целевого объекта канала, связанного с конечной точкой IN, выполните следующие действия:

Настройка непрерывного чтения в драйвере клиента UMDF

  1. Вызов QueryInterface в целевом объекте канала (IWDFUsbTargetPipe) и запросите интерфейс IWDFUsbTargetPipe2.

  2. Вызов QueryInterface в объекте обратного вызова устройства и запрос к интерфейсу IUsbTargetPipeContinuousReaderCallbackReadComplete. Чтобы использовать непрерывное средство чтения, необходимо реализовать IUsbTargetPipeContinuousReaderCallbackReadComplete. Реализация описана далее в этом разделе.

  3. Вызов QueryInterface на объект обратного вызова устройства и запрос к интерфейсу IUsbTargetPipeContinuousReaderCallbackReadersFailed , если вы реализовали обратный вызов сбоя. Реализация описана далее в этом разделе.

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

    Метод настраивает непрерывное средство чтения для целевого объекта канала. Непрерывное средство чтения создает очереди, которые управляют набором запросов на чтение по мере их отправки и получения из целевого объекта канала.

В следующем примере кода настраивается непрерывное средство чтения для указанного целевого объекта канала. В примере предполагается, что объект целевого канала, указанный вызывающим объектом, связан с конечной точкой IN. Непрерывное средство чтения настроено для чтения USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE байтов; для использования числа ожидающих запросов по умолчанию с помощью платформы; для вызова предоставленных драйвером клиента методов завершения и обратного вызова сбоя. Полученный буфер не будет содержать данные заголовка или трейлера.

HRESULT CDeviceCallback::ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe)
{
    if (!pFxPipe)
    {
        return E_INVALIDARG;
    }

    IUsbTargetPipeContinuousReaderCallbackReadComplete *pOnCompletionCallback = NULL;
    IUsbTargetPipeContinuousReaderCallbackReadersFailed *pOnFailureCallback = NULL;
    IWDFUsbTargetPipe2* pFxUsbPipe2 = NULL;

    HRESULT hr = S_OK;

    // Set up the continuous reader to read from the target pipe object.

    //Get a pointer to the target pipe2 object.
    hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&pFxUsbPipe2));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the completion callback.
    hr = QueryInterface(IID_PPV_ARGS(&pOnCompletionCallback));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the failure callback.
    hr = QueryInterface(IID_PPV_ARGS(&pOnFailureCallback));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the target pipe2 object.
    hr = pFxUsbPipe2->ConfigureContinuousReader (
        USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE, //size of data to be read
        0, //Header
        0, //Trailer
        0, // Number of pending requests queued by WDF
        NULL, // Cleanup callback. Not provided.
        pOnCompletionCallback, //Completion routine.
        NULL, //Completion routine context. Not provided.
        pOnFailureCallback); //Failure routine. Not provided

    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

ConfigureContinuousReaderExit:

    if (pOnFailureCallback)
    {
        pOnFailureCallback->Release();
        pOnFailureCallback = NULL;
    }

    if (pOnCompletionCallback)
    {
        pOnCompletionCallback->Release();
        pOnCompletionCallback = NULL;
    }

    if (pFxUsbPipe2)
    {
        pFxUsbPipe2->Release();
        pFxUsbPipe2 = NULL;
    }

    return hr;
}

Затем укажите состояние целевого объекта канала, когда устройство входит и выходит из рабочего состояния (D0).

Если драйвер клиента использует управляемую питанием очередь для отправки запросов в канал, очередь отправляет запросы только в том случае, если устройство находится в состоянии D0 . Если состояние питания устройства изменяется с D0 на более низкое состояние питания (при выходе D0), целевой объект канала завершает ожидающие запросы, а очередь перестает отправлять запросы целевому объекту канала. Поэтому драйвер клиента не требует запуска и остановки целевого объекта канала.

Непрерывное средство чтения не использует управляемые питанием очереди для отправки запросов. Поэтому при изменении состояния питания устройства необходимо явно запустить или остановить целевой объект канала. Для изменения состояния целевого объекта канала можно использовать интерфейс IWDFIoTargetStateManagement , реализованный платформой. После получения указателя на интерфейс IWDFUsbTargetPipe целевого объекта канала, связанного с конечной точкой IN, выполните следующие действия:

Реализация управления состоянием

  1. В реализации IPnpCallbackHardware::OnPrepareHardware вызовите QueryInterface в целевом объекте канала (IWDFUsbTargetPipe) и запросите интерфейс IWDFIoTargetStateManagement. Сохраните ссылку в переменной члена класса обратного вызова устройства.

  2. Реализуйте интерфейс IPnpCallback в объекте обратного вызова устройства.

  3. В реализации метода IPnpCallback::OnD0Entry вызовите IWDFIoTargetStateManagement::Start для запуска непрерывного чтения.

  4. В реализации метода IPnpCallback::OnD0Exit вызовите IWDFIoTargetStateManagement::Stop, чтобы остановить непрерывное средство чтения.

После ввода устройства рабочего состояния (D0) платформа вызывает предоставленный клиентом драйвер D0-entry обратный вызов, который запускает целевой объект канала. Когда устройство покидает состояние D0, платформа вызывает метод обратного вызова D0-exit . Объект целевого канала завершает количество ожидающих запросов на чтение, настроенных драйвером клиента, и останавливает прием новых запросов. В следующем примере кода реализован интерфейс IPnpCallback в объекте обратного вызова устройства.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

В следующем примере кода показано, как получить указатель на интерфейс IWDFIoTargetStateManagement объекта целевого канала в методе IPnpCallback::OnPrepareHardware

   //Enumerate the endpoints and get the interrupt pipe.
    for (UCHAR index = 0; index < NumEndpoints; index++)
    {
        hr = pFxInterface->RetrieveUsbPipeObject(index, &pFxPipe);

        if (SUCCEEDED (hr) && pFxPipe)
        {
            if ((pFxPipe->IsInEndPoint()) && (pFxPipe->GetType()==UsbdPipeTypeInterrupt))
            {
                //Pipe is for an interrupt IN endpoint.
                hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&m_pFxIoTargetInterruptPipeStateMgmt));

                if (m_pFxIoTargetInterruptPipeStateMgmt)
                {
                    m_pFxUsbPipe = pFxPipe;
                    break;
                }

            }
            else
            {
                //Pipe is NOT for an interrupt IN endpoint.
                pFxPipe->Release();
                pFxPipe = NULL;
            }
        }
        else
        {
             //Pipe not found.
        }
    }

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

 HRESULT CDeviceCallback::OnD0Entry(
    IWDFDevice*  pWdfDevice,
    WDF_POWER_DEVICE_STATE  previousState
    )
{

    if (!m_pFxIoTargetInterruptPipeStateMgmt)
    {
        return E_FAIL;
    }

    HRESULT hr = m_pFxIoTargetInterruptPipeStateMgmt->Start();

    if (FAILED (hr))
    {
        goto OnD0EntryExit;
    }

OnD0EntryExit:
    return hr;
}

HRESULT CDeviceCallback::OnD0Exit(
    IWDFDevice*  pWdfDevice,
    WDF_POWER_DEVICE_STATE  previousState
    )
{
    if (!m_pFxIoTargetInterruptPipeStateMgmt)
    {
        return E_FAIL;
    }

    // Stop the I/O target always succeeds.
    (void)m_pFxIoTargetInterruptPipeStateMgmt->Stop(WdfIoTargetCancelSentIo);

    return S_OK;
}

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

Предоставление обратного вызова завершения путем реализации IUsbTargetPipeContinuousReaderCallbackReadComplete

  1. Реализуйте интерфейс IUsbTargetPipeContinuousReaderCallbackReadComplete на объекте обратного вызова устройства.

  2. Убедитесь, что реализация объекта обратного вызова устройства увеличивает число ссылок объекта обратного вызова, а затем возвращает указатель интерфейса IUsbTargetPipeContinuousReaderCallbackReadComplete .

  3. В реализации метода IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion доступ к данным, считываемым из канала. Параметр pMemory указывает на память, выделенную платформой, содержащей данные. Для получения буфера, содержащего данные, можно вызвать IWDFMemory::GetDataBuffer. Буфер содержит заголовок, однако длина данных, указанная параметром NumBytesTransferred onReaderCompletion, не включает длину заголовка. Длина заголовка задается драйвером клиента при настройке непрерывного чтения в вызове драйвера iWDFUsbTargetPipe2 ::ConfigureContinuousReader.

  4. Укажите указатель на обратный вызов завершения в параметре pOnCompletion метода IWDFUsbTargetPipe2::ConfigureContinuousReader.

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

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

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
    public IUsbTargetPipeContinuousReaderCallbackReadComplete

{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

    virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

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

HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
    if (ppvObject == NULL)
    {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;

    HRESULT hr = E_NOINTERFACE;

    if(  IsEqualIID(riid, __uuidof(IPnpCallbackHardware))   ||  IsEqualIID(riid, __uuidof(IUnknown))  )
    {
        *ppvObject = static_cast<IPnpCallbackHardware*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IPnpCallback)))
    {
        *ppvObject = static_cast<IPnpCallback*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    return hr;
}

В следующем примере кода показано, как получить данные из буфера, возвращаемого IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion. Каждый раз, когда целевой объект канала успешно завершает запрос на чтение, платформа вызывает OnReaderCompletion. В примере получается буфер, содержащий данные и печатающий содержимое в выходных данных отладчика.

 VOID CDeviceCallback::OnReaderCompletion(
    IWDFUsbTargetPipe* pPipe,
    IWDFMemory* pMemory,
    SIZE_T NumBytesTransferred,
    PVOID Context)
{
    if (pPipe != m_pFxUsbInterruptPipe)
    {
        return;
    }

    if (NumBytesTransferred == 0)
    {
        // NumBytesTransferred is zero.
        return;
    }

    PVOID pBuff = NULL;
    LONG CurrentData = 0;
    char data[20];

    pBuff = pMemory->GetDataBuffer(NULL);

    if (pBuff)
    {
        CopyMemory(&CurrentData, pBuff, sizeof(CurrentData));
        sprintf_s(data, 20, "%d\n", CurrentData);
        OutputDebugString(data);
        pBuff = NULL;
    }
    else
    {
        OutputDebugString(TEXT("Unable to get data buffer."));
    }
}

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

Предоставление обратного вызова сбоя путем реализации IUsbTargetPipeContinuousReaderCallbackReadersFailed

  1. Реализуйте интерфейс IUsbTargetPipeContinuousReaderCallbackReadersFailed на объекте обратного вызова устройства.

  2. Убедитесь, что реализация объекта обратного вызова устройства увеличивает число ссылок объекта обратного вызова, а затем возвращает указатель интерфейса IUsbTargetPipeContinuousReaderCallbackReadersFailed .

  3. В реализации метода IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure предоставьте обработку ошибок неудачного запроса на чтение.

    Если непрерывное средство чтения не завершит запрос на чтение и драйвер клиента предоставляет обратный вызов сбоя, платформа вызывает метод IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure. Платформа предоставляет значение HRESULT в параметре hrStatus , указывающее код ошибки, который произошел в целевом объекте канала. На основе этого кода ошибки может быть предоставлена определенная обработка ошибок. Например, если требуется, чтобы платформа сбрасывала канал, а затем перезапустить непрерывное средство чтения, убедитесь, что обратный вызов возвращает ЗНАЧЕНИЕ TRUE.

    Примечание. Не вызывайте IWDFIoTargetStateManagement::Start и IWDFIoTargetStateManagement::Stop в обратном вызове сбоя.

  4. Укажите указатель на обратный вызов сбоя в параметре pOnFailure метода IWDFUsbTargetPipe2::ConfigureContinuousReader.

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

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
    public IUsbTargetPipeContinuousReaderCallbackReadComplete,
    public IUsbTargetPipeContinuousReaderCallbackReadersFailed
{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

    virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);
    virtual BOOL STDMETHODCALLTYPE OnReaderFailure(IWDFUsbTargetPipe * pPipe, HRESULT hrCompletion);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbInterruptPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT RetrieveUSBDeviceDescriptor (IWDFUsbTargetDevice* pUSBTargetDevice, PUSB_DEVICE_DESCRIPTOR DescriptorHeader, PULONG cbDescriptor);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

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

HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
    if (ppvObject == NULL)
    {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;

    HRESULT hr = E_NOINTERFACE;

    if(  IsEqualIID(riid, __uuidof(IPnpCallbackHardware))   ||  IsEqualIID(riid, __uuidof(IUnknown))  )
    {
        *ppvObject = static_cast<IPnpCallbackHardware*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;

    }

    if(  IsEqualIID(riid, __uuidof(IPnpCallback)))
    {
        *ppvObject = static_cast<IPnpCallback*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadersFailed)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadersFailed*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    return hr;
}

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

 BOOL CDeviceCallback::OnReaderFailure(
    IWDFUsbTargetPipe * pPipe,
    HRESULT hrCompletion
    )
{
    UNREFERENCED_PARAMETER(pPipe);
    UNREFERENCED_PARAMETER(hrCompletion);
    return TRUE;
}

Если драйвер клиента не предоставляет обратный вызов сбоя и возникает ошибка, платформа сбрасывает USB-канал и перезапускает непрерывное средство чтения.