//
//    Copyright (C) Microsoft.  All rights reserved.
//
#include "fxusbpch.hpp"

extern "C" {
#include "FxUsbPipe.tmh"
}

#include "Fxglobals.h"
//
//  NOTE: There are 3 different paths Requests could be sent to the lower driver
//  1) In case of reposting a successfully completed Request use the Dpc which calls SendIo.
//  2) For a failed completion use a workitem which works if the IoTarget is in Started state.
//  3) On moving to the start state the Repeater requests are inserted into the
//      Iotargets Pended Queue directly.
//
//  The function ResubmitRepeater calls SubmitLocked. If the action we get back
//  from SubmitLocked is "SubmitSend", (SubmitQueued is treated as failure because
//  of WDF_REQUEST_SEND_INTERNAL_OPTION_FAIL_ON_PEND flag) we are guaranteed to
//  call IoCallDriver in the workitem or  the DPC and hence the completion routine
//  being called.
//  This is very important because we increment the m_IoCount in SubmitLocked and
//  decrement in the completion routine.  So if there was a code path where
//  SubmitLocked was called and IoCallDriver(hence the completion routine) wasn't,
//  the IoTarget could stop responding in Dispose.
//


FxUsbPipeContinuousReader::FxUsbPipeContinuousReader(
    __in FxUsbPipe* Pipe,
    __in UCHAR NumReaders
    ) :
    m_NumReaders(NumReaders),
    m_NumFailedReaders(0)
{
    m_WorkItem = NULL;
    m_WorkItemRerunContext = NULL;
    m_WorkItemThread = NULL;
    m_WorkItemFlags = 0;
    m_WorkItemQueued = FALSE;
    m_ReadersSubmitted = FALSE;

    m_Lookaside = NULL;
    m_Pipe = Pipe;

    m_TargetDevice = m_Pipe->GetTargetDevice();

    RtlZeroMemory(&m_Readers[0], m_NumReaders * sizeof(FxUsbPipeRepeatReader));
}

FxUsbPipeContinuousReader::~FxUsbPipeContinuousReader()
{
    LONG i;

    FxUsbPipeRepeatReader * reader = &m_Readers[0];

    //
    // It is impoortant to delete the requests before the lookaside because the
    // requests may have outstanding references on memory objects allocated by
    // the lookaside.  The lookaside will not be truly deleted until the oustanding
    // memory object allocations are also freed.
    //
    for (i = 0; i < m_NumReaders; i++) {
        if (reader[i].Request != NULL) {
            DeleteMemory(reader[i].Request);

            reader[i].Request->DeleteObject();
            reader[i].Request = NULL;
        }

#if (FX_CORE_MODE == FX_CORE_USER_MODE)
        reader[i].ReadCompletedEvent.Uninitialize();
        reader[i].m_ReadWorkItem.Free();
#endif
    }

    if (m_Lookaside != NULL) {
        m_Lookaside->DeleteObject();
    }

    if (m_WorkItem != NULL) {
        m_WorkItem->DeleteObject();
        m_WorkItem = NULL;
    }
}

BOOLEAN
FxUsbPipeContinuousReader::QueueWorkItemLocked(
    __in FxUsbPipeRepeatReader* Repeater
    )
{
    BOOLEAN queued;
    PFX_DRIVER_GLOBALS fxDriverGlobals;

    queued = FALSE;
    fxDriverGlobals = m_Pipe->GetDriverGlobals();

    if (m_Pipe->m_State == WdfIoTargetStarted && m_WorkItemQueued == FALSE) {
        //
        // No item queued, queue it up now.
        //
        DoTraceLevelMessage(
            fxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p continuous reader queueing work item to recover "
            "from failed allocation", m_Pipe->GetHandle());

        if (m_WorkItem->Enqueue(_FxUsbPipeRequestWorkItemThunk, Repeater)) {
            m_WorkItemQueued = TRUE;
            queued = TRUE;
        }
        else {
            DoTraceLevelMessage(fxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGIO,
                        "Could not Queue workitem");
        }
    }

    //
    // We only want to queue the work item while the target is in the
    // started state.  If it is not started, then we are no longer sending
    // i/o and we should not queue the work item to try to restart.
    //
    if (FALSE == queued) {
        DoTraceLevelMessage(
            fxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p continuous reader not queueing work item,"
            "WorkItemQueued = %d, target state %!WDF_IO_TARGET_STATE!",
            m_Pipe->GetHandle(), m_WorkItemQueued, m_Pipe->m_State);
    }

    return queued;
}

ULONG
FxUsbPipeContinuousReader::ResubmitRepeater(
    __in FxUsbPipeRepeatReader* Repeater,
    __out NTSTATUS* Status
    )
{
    PFX_DRIVER_GLOBALS pFxDriverGlobals;
    NTSTATUS status;
    ULONG action;
    KIRQL irql;

    action              = 0;
    pFxDriverGlobals    = m_Pipe->GetDriverGlobals();
    status              = STATUS_UNSUCCESSFUL;

    //
    // Reformat and allocate any new needed buffers
    //
    status = FormatRepeater(Repeater);

    m_Pipe->Lock(&irql);

    //
    // Do not re-submit repeaters if there is a queued/running work-item to
    // reset pipe. Work-item will restart this repeater later.
    // This check needs to be done after the FormatRepeater() call above to
    // prevent a race condition where we are not detecting when the repeater
    // is cancelled.
    //
    if (m_WorkItemQueued) {
        //
        // Return an error and no action flags to let the caller know that
        // this request was not sent.
        //
        status = STATUS_CANCELLED;

        DoTraceLevelMessage(
            pFxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p is being reset, continuous reader %p FxRequest %p"
            " PIRP %p is deferred for later.",
            m_Pipe->GetHandle(), Repeater, Repeater->Request,
            Repeater->RequestIrp);
    }
    else if (NT_SUCCESS(status)) {
        //
        // Get ready to re-submit the repeater.
        //
        action = m_Pipe->SubmitLocked(
            Repeater->Request,
            NULL,
            WDF_REQUEST_SEND_INTERNAL_OPTION_FAIL_ON_PEND
            );

        if (action & SubmitSend) {
            //
            // Clear the event only if we are going to send the request
            //
            Repeater->ReadCompletedEvent.Clear();
        }
        else if (action & SubmitQueued) {
            //
            // Request got canceled asynchronously. The other thread is now
            // responsible for calling its completion callback.
            //
            status = STATUS_CANCELLED;
        }
        else {
            //
            // Submit failed (which is expected when we are changing the target
            // state or when the request is canceled).  It should always be an
            // error.
            //
            status = Repeater->Request->GetFxIrp()->GetStatus();
            ASSERT(!NT_SUCCESS(status));
        }
    }
    else {
        //
        // Could not allocate a new buffer
        //
        Repeater->Request->GetFxIrp()->SetStatus(status);

        DoTraceLevelMessage(
            pFxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p continuous reader, format failed, %!STATUS!, "
            "repeater %p", m_Pipe->GetHandle(), status, Repeater);

        if (m_Pipe->m_State == WdfIoTargetStarted) {
            m_NumFailedReaders++;
            ASSERT(m_NumFailedReaders <= m_NumReaders);

            if (m_NumFailedReaders == m_NumReaders) {
                //
                // Queue a work item to clear problem.
                //
                QueueWorkItemLocked(Repeater);
            }
            else {
                DoTraceLevelMessage(
                    pFxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
                    "WDFUSBPIPE %p continuous reader, buffer alloc failed, but "
                    "there are %d readers left out of a max of %d",
                    m_Pipe->GetHandle(), m_NumReaders - m_NumFailedReaders,
                    m_NumReaders);

                //
                // There are still other pending readers, just use those for
                // now.
                //
                DO_NOTHING();
            }
        }
        else {
            DoTraceLevelMessage(
                pFxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
                "WDFUSBPIPE %p continuous reader, buffer alloc failed, but not "
                "in started state", m_Pipe->GetHandle());
        }
    }

    m_Pipe->Unlock(irql);

    *Status = status;

    return action;
}

VOID
FxUsbPipeContinuousReader::_FxUsbPipeRequestComplete(
    __in WDFREQUEST Request,
    __in WDFIOTARGET Target,
    __in PWDF_REQUEST_COMPLETION_PARAMS Params,
    __in WDFCONTEXT Context
    )
{
    FxUsbPipeRepeatReader* pRepeater;
    FxUsbPipeContinuousReader* pThis;
    FxUsbPipe* pPipe;
    NTSTATUS status;
    ULONG action;
    BOOLEAN readCompletedEventSet;

    UNREFERENCED_PARAMETER(Request);
    UNREFERENCED_PARAMETER(Params);

    readCompletedEventSet = FALSE;
    action = 0;
    pRepeater = (FxUsbPipeRepeatReader*) Context;
    pThis = (FxUsbPipeContinuousReader*) pRepeater->Parent;
    pPipe = pThis->m_Pipe;

    status = pRepeater->Request->GetFxIrp()->GetStatus();

    if (NT_SUCCESS(status)) {
        PWDF_USB_REQUEST_COMPLETION_PARAMS params;

        params  = pRepeater->Request->GetContext()->
            m_CompletionParams.Parameters.Usb.Completion;

        pThis->m_ReadCompleteCallback((WDFUSBPIPE) Target,
                                      params->Parameters.PipeRead.Buffer,
                                      params->Parameters.PipeRead.Length,
                                      pThis->m_ReadCompleteContext);

        //
        // This will release the reference on the read memory and allocate a new
        // one
        //
        action = pThis->ResubmitRepeater(pRepeater, &status);
    }
    else if (status != STATUS_CANCELLED) {
        KIRQL irql;

        DoTraceLevelMessage(
            pPipe->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p continuous reader FxRequest %p PIRP %p returned with "
            "%!STATUS!", pPipe->GetHandle(), pRepeater->Request ,
            pRepeater->RequestIrp, status);


        pPipe->Lock(&irql);

        pRepeater->ReadCompletedEvent.Set();
        readCompletedEventSet = TRUE;

        //
        // Queue a work item to clear problem.
        //
        pThis->QueueWorkItemLocked(pRepeater);

        pPipe->Unlock(irql);

        ASSERT(!NT_SUCCESS(status));
    }
    else {
        //
        // I/O was cancelled, which means internally it was cancelled so don't
        // do anything.
        //
        DoTraceLevelMessage(
            pPipe->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p continuous reader %p FxRequest %p PIRP %p canceled",
            pPipe->GetHandle(), pRepeater, pRepeater->Request , pRepeater->RequestIrp);

        DO_NOTHING();
    }

    if (action & SubmitSend) {

        //
        // We don't want to recurse on the same stack and overflow it.
        // This is especially true if the device is pushing a lot of data and
        // usb is completing everything within its dpc as soon as we send the
        // read down.  Eventually on a chk build, we will be nailed for running
        // in one DPC for too long.
        //
        // As a slower alternative, we could queue a work item and resubmit the
        // read from there.
        //

#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
        BOOLEAN result;
        result = KeInsertQueueDpc(&pRepeater->Dpc, NULL, NULL);

        //
        // The DPC should never be currently queued when we try to queue it.
        //
        ASSERT(result != FALSE);
#else
        pRepeater->m_ReadWorkItem.Enqueue((PMX_WORKITEM_ROUTINE)_ReadWorkItem, pRepeater);
#endif
        UNREFERENCED_PARAMETER(status); //for fre build

    }
    else if (action & SubmitQueued) {
        //
        // I/O got canceled asynchronously; the other thread is now
        // responsible for re-invoking this completion routine.
        //
        ASSERT(STATUS_CANCELLED == status);

        DoTraceLevelMessage(
            pPipe->GetDriverGlobals(), TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p continuous reader %p FxRequest %p PIRP %p got"
            " asynchronously canceled",
            pPipe->GetHandle(), pRepeater, pRepeater->Request ,
            pRepeater->RequestIrp);

        DO_NOTHING();
    }
    else if (FALSE == readCompletedEventSet) {
        ASSERT(!NT_SUCCESS(status));
        //
        // We are not sending the request and it is not queued so signal that
        // it is done.
        //
        pRepeater->ReadCompletedEvent.Set();
    }
}

VOID
FxUsbPipeContinuousReader::FxUsbPipeRequestWorkItemHandler(
    __in FxUsbPipeRepeatReader* FailedRepeater
    )
{
    FxUsbDevice* pDevice;
    NTSTATUS status, failedStatus;
    USBD_STATUS usbdStatus;
    LONG i;
    KIRQL irql;
    BOOLEAN restart;
    FxRequestContext* context;
    PWDF_USB_REQUEST_COMPLETION_PARAMS usbCompletionParams;
    PFX_DRIVER_GLOBALS pFxDriverGlobals;

    pFxDriverGlobals = m_Pipe->GetDriverGlobals();

    //
    // Get failed info.
    //
    failedStatus = FailedRepeater->Request->GetStatus();

    //
    // Context is allocated at config time and gets reused so context
    // will never be NULL.
    //
    context = FailedRepeater->Request->GetContext();
    usbCompletionParams = context->m_CompletionParams.Parameters.Usb.Completion;

    //
    // In case FormatRepeater fails to allocate memory usbCompletionParams
    // pointer is not set.
    //
    // usbCompletionParams are part of the context and
    // not really allocated at the time of every Format but
    // the pointer gets cleared by request->Reuse and gets set again by
    // context->SetUsbType.
    //
    // In FormatRepeater, context->SetUsbType is skipped
    // if a memory failure occurs before this step.
    //
    // Hence retrieve usbdStatus only when usbCompletionParams is set.
    //
    if (usbCompletionParams) {
        usbdStatus = usbCompletionParams->UsbdStatus;
    }
    else {
        //
        // Set usbdStatus to success as we didn't receive a failure from
        // USB stack.
        //
        // This path is reached during memory allocation failure. In such
        // case failedStatus would already be set appropriately. (usbdStatus
        // and failedStatus are passed to m_ReadersFailedCallback below.)
        //
        usbdStatus = STATUS_SUCCESS;
    }

    //
    // No read requests should be in progress when the framework calls the
    // EvtUsbTargetPipeReadersFailed callback function. This is part of the
    // contract so that the Driver doesn't need to bother with the
    // Completion calllback while taking corrective action.
    //
    CancelRepeaters();
    pDevice = m_Pipe->m_UsbDevice;

    if (m_ReadersFailedCallback != NULL) {
        //
        // Save the current thread object pointer. This value is
        // used for not deadlocking when misbehaved drivers (< v1.9) call
        // WdfIoTargetStop from EvtUsbTargetPipeReadersFailed callback
        //
        ASSERT(NULL == m_WorkItemThread);
        m_WorkItemThread = Mx::MxGetCurrentThread();

        restart = m_ReadersFailedCallback(
            (WDFUSBPIPE) m_Pipe->GetHandle(),
            failedStatus,
            usbdStatus
            );

        m_WorkItemThread = NULL;
    }
    else {
        //
        // By default, we restart the readers
        //
        restart = TRUE;
    }

    if (restart) {
        status = pDevice->IsConnected();

        if (NT_SUCCESS(status)) {

            //
            // for v1.9 or higher use the error recovery procedure prescribed
            // by the USB team.
            //
            if (pFxDriverGlobals->IsVersionGreaterThanOrEqualTo(1,9)) {

                if (pDevice->IsEnabled()) {
                    //
                    // Reset the pipe if port status is enabled
                    //
                    m_Pipe->Reset();
                }
                else {
                    //
                    // Reset the device if port status is disabled
                    //
                    status = pDevice->Reset();
                }
            }
            else {
                //
                // Reset the device if port status is disabled
                //
                status = pDevice->Reset();
            }
        }
        else {
            //
            // if port status is disconnected we would get back
            // a !NT_SUCCESS. This would mean that we would not
            // send the readers again and treat it like a failed reader.
            //
            DO_NOTHING();
        }

    }
    else {
        //
        // By setting status to !NT_SUCCESS, we will not send the readers
        // again and treat it like a failed reader.
        //
        status = STATUS_UNSUCCESSFUL;
    }

    //
    // Work item is no longer queued.  We set this before resubmitting the
    // repeaters so that if they all complete and fail, we will requeue the
    // work item.
    //
    m_Pipe->Lock(&irql);
    m_WorkItemQueued = FALSE;
    m_Pipe->Unlock(irql);

    if (NT_SUCCESS(status)) {
        ULONG action;

        //
        // Reset the count to zero.  This is safe since we stopped all the
        // readers at the beginning of this function.
        //
        m_NumFailedReaders = 0;

        //
        // restart the readers
        //
        for (i = 0; i < m_NumReaders; i++) {
            FxUsbPipeRepeatReader* pRepeater;

            pRepeater = &m_Readers[i];

            action = ResubmitRepeater(pRepeater, &status);

            if (action & SubmitSend) {
                //
                // Ignore the return value because once we have sent the
                // request, we want all processing to be done in the
                // completion routine.
                //
                (void) pRepeater->Request->GetSubmitFxIrp()->CallDriver(m_Pipe->m_TargetDevice);
            }
        }
    }
}

VOID
FxUsbPipeContinuousReader::_FxUsbPipeRequestWorkItemThunk(
    __in PVOID Context
    )
/*
    Only one work-item can be in-progress at any given time and
    only one additional work-item can be queued at any given time.
    This logic and m_WorkItemQueued makes this happen.
*/
{
    FxUsbPipeRepeatReader* pFailedRepeater;
    FxUsbPipeContinuousReader* pThis;
    FxUsbPipe* pPipe;
    KIRQL irql;
    BOOLEAN rerun, inprogress;

    pFailedRepeater = (FxUsbPipeRepeatReader*) Context;
    pThis = (FxUsbPipeContinuousReader*) pFailedRepeater->Parent;
    pPipe = pThis->m_Pipe;

    //
    // Check if a work item is already in progress.
    //
    pPipe->Lock(&irql);
    if (pThis->m_WorkItemFlags & FX_USB_WORKITEM_IN_PROGRESS) {
        //
        // Yes, just let the other thread re-run this logic.
        //
        inprogress = TRUE;

        ASSERT((pThis->m_WorkItemFlags & FX_USB_WORKITEM_RERUN) == 0);
        pThis->m_WorkItemFlags |= FX_USB_WORKITEM_RERUN;

        ASSERT(NULL == pThis->m_WorkItemRerunContext);
        pThis->m_WorkItemRerunContext = Context;
    }
    else {
        //
        // No, it not running.
        //
        inprogress = FALSE;

        pThis->m_WorkItemFlags |= FX_USB_WORKITEM_IN_PROGRESS;
        ASSERT((pThis->m_WorkItemFlags & FX_USB_WORKITEM_RERUN) == 0);
    }
    pPipe->Unlock(irql);

    if (inprogress) {
        return;
    }

    //
    // OK, this thread is responsible for running the work item logic.
    //
    do {
        //
        // Cleanup and restart the repeters.
        //
        pThis->FxUsbPipeRequestWorkItemHandler(pFailedRepeater);

        //
        // Check if callback needs to be re-run.
        //
        pPipe->Lock(&irql);
        if (pThis->m_WorkItemFlags & FX_USB_WORKITEM_RERUN) {
            //
            // Yes, a new work item was requested while it was already running.
            //
            rerun = TRUE;

            pThis->m_WorkItemFlags &= ~FX_USB_WORKITEM_RERUN;

            ASSERT(pThis->m_WorkItemRerunContext != NULL);
            pFailedRepeater = (FxUsbPipeRepeatReader*)pThis->m_WorkItemRerunContext;
            pThis->m_WorkItemRerunContext = NULL;

            ASSERT(pThis == (FxUsbPipeContinuousReader*)pFailedRepeater->Parent);
        }
        else {
            //
            // No, all done.
            //
            rerun = FALSE;

            ASSERT(pThis->m_WorkItemFlags & FX_USB_WORKITEM_IN_PROGRESS);
            pThis->m_WorkItemFlags &= ~FX_USB_WORKITEM_IN_PROGRESS;

            ASSERT(NULL == pThis->m_WorkItemRerunContext);
        }
        pPipe->Unlock(irql);

    }
    while (rerun);
}

PVOID
FxUsbPipeContinuousReader::operator new(
    __in size_t Size,
    __in PFX_DRIVER_GLOBALS FxDriverGlobals,
    __range(1, NUM_PENDING_READS_MAX) ULONG NumReaders
    )
{
    ASSERT(NumReaders >= 1);

    return FxPoolAllocate(
        FxDriverGlobals,
        NonPagedPool,
        Size + (NumReaders-1) * sizeof(FxUsbPipeRepeatReader)
        );
}

_Must_inspect_result_
NTSTATUS
FxUsbPipeContinuousReader::FormatRepeater(
    __in FxUsbPipeRepeatReader* Repeater
    )
{
    WDF_REQUEST_REUSE_PARAMS params;
    FxRequestBuffer buf;
    FxUsbPipeTransferContext* pContext;
    FxMemoryObject* pMemory;
    FxRequest* pRequest;
    NTSTATUS status;

    pRequest = Repeater->Request;
    //
    // The repeater owns the request memory.  If there is a memory on the
    // context, delete it now.  the memory will still be referencable since
    // it will still have a reference against it until FormatTransferRequest is
    // called or the request is freed and the context releases its references
    //
    DeleteMemory(pRequest);

    WDF_REQUEST_REUSE_PARAMS_INIT(&params, 0, STATUS_NOT_SUPPORTED);

    pRequest->Reuse(&params);

    //
    // pMemory will be deleted when either
    // a) The request completes
    // or
    // b) The continuous reader is destroyed and we delete the lookaside.  since
    //    the lookaside is the parent object for pMemory, pMemory will be disposed
    //    of when the parent is Disposed
    //
    status = m_Lookaside->Allocate(&pMemory);
    if (!NT_SUCCESS(status)) {
        FxRequestContext* pContext;

        pContext = pRequest->GetContext();
        if (pContext != NULL ) {
           pContext->m_RequestMemory = NULL;
        }

        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(pMemory->GetBuffer(), pMemory->GetBufferSize());

    buf.SetMemory(pMemory, &m_Offsets);

    status = m_Pipe->FormatTransferRequest(
        pRequest,
        &buf,
        USBD_TRANSFER_DIRECTION_IN | USBD_SHORT_TRANSFER_OK
        );

    if (!NT_SUCCESS(status)) {
        //
        // In the case of failure, the context in the request will delete the
        // memory.  If there is no context, delete the memory here.
        //
        if (pRequest->GetContext() == NULL) {
            //
            // Use DeleteFromFailedCreate because the driver never saw the
            // buffer, so they shouldn't be told about it going away.
            //
            pMemory->DeleteFromFailedCreate();
        }

        return status;
    }

    pContext = (FxUsbPipeTransferContext*) pRequest->GetContext();
    pContext->SetUsbType(WdfUsbRequestTypePipeRead);
    pContext->m_UsbParameters.Parameters.PipeRead.Buffer = (WDFMEMORY)
        pMemory->GetObjectHandle();

    pRequest->SetCompletionRoutine(_FxUsbPipeRequestComplete, Repeater);
    return status;
}


VOID
FxUsbPipeContinuousReader::CancelRepeaters(
    VOID
    )
{
    LONG i;

    Mx::MxEnterCriticalRegion();

    for (i = 0; i < m_NumReaders; i++) {
        m_Readers[i].Request->Cancel();
        m_Pipe->GetDriverGlobals()->WaitForSignal(
                m_Readers[i].ReadCompletedEvent.GetSelfPointer(),
                "waiting for continuous reader to finish, WDFUSBPIPE",
                m_Pipe->GetHandle(),
                m_Pipe->GetDriverGlobals()->FxVerifierDbgWaitForSignalTimeoutInSec,
                WaitSignalBreakUnderVerifier);

    }

    Mx::MxLeaveCriticalRegion();
    //
    // Checking for IO Count <= 1 is not a good idea here because there could be always other IO
    // besides that from the continous reader going on the Read Pipe.
    //
}

FxUsbPipeTransferContext::FxUsbPipeTransferContext(
    __in FX_URB_TYPE FxUrbType
    ) :
    FxUsbRequestContext(FX_RCT_USB_PIPE_XFER)
{
    m_UnlockPages = FALSE;
    m_PartialMdl = NULL;
    m_USBDHandle = NULL;

    if (FxUrbType == FxUrbTypeLegacy) {
        m_Urb = &m_UrbLegacy;
    }
    else {
        m_Urb = NULL;
    }

}

FxUsbPipeTransferContext::~FxUsbPipeTransferContext(
    VOID
    )
{
    if (m_Urb && (m_Urb != &m_UrbLegacy)) {
        USBD_UrbFree(m_USBDHandle, (PURB)m_Urb);
    }
    m_Urb = NULL;
    m_USBDHandle = NULL;
}

__checkReturn
NTSTATUS
FxUsbPipeTransferContext::AllocateUrb(
    __in USBD_HANDLE USBDHandle
    )
{
    NTSTATUS status;

    if (m_Urb) {
        status = STATUS_INVALID_DEVICE_STATE;
        goto Done;
    }

    status = USBD_UrbAllocate(USBDHandle, (PURB*)&m_Urb);

    if (!NT_SUCCESS(status)) {
        goto Done;
    }

    m_USBDHandle = USBDHandle;

Done:
    return status;
}

VOID
FxUsbPipeTransferContext::Dispose(
    VOID
    )
{
    if (m_Urb && (m_Urb != &m_UrbLegacy)){
        USBD_UrbFree(m_USBDHandle, (PURB) m_Urb);
        m_Urb = NULL;
        m_USBDHandle = NULL;
    }
}

VOID
FxUsbPipeTransferContext::ReleaseAndRestore(
    __in FxRequestBase* Request
    )
{
#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
    //
    // Check now because Init will NULL out the field
    //
    if (m_PartialMdl != NULL) {
        if (m_UnlockPages) {
            MmUnlockPages(m_PartialMdl);
            m_UnlockPages = FALSE;
        }

        FxMdlFree(Request->GetDriverGlobals(), m_PartialMdl);
        m_PartialMdl = NULL;
    }
#endif
    FxUsbRequestContext::ReleaseAndRestore(Request); // __super call
}

VOID
FxUsbPipeTransferContext::CopyParameters(
    __in FxRequestBase* Request
    )
{
    m_CompletionParams.IoStatus.Information = GetUrbTransferLength();

    //
    // If both are at the same offset, we don't have to compare type for
    // Read or Write
    //
    WDFCASSERT(FIELD_OFFSET(WDF_USB_REQUEST_COMPLETION_PARAMS,
                            Parameters.PipeRead.Length) ==
               FIELD_OFFSET(WDF_USB_REQUEST_COMPLETION_PARAMS,
                            Parameters.PipeWrite.Length));

    m_UsbParameters.Parameters.PipeRead.Length = GetUrbTransferLength();
    FxUsbRequestContext::CopyParameters(Request); // __super call
}

VOID
FxUsbPipeTransferContext::SetUrbInfo(
    __in USBD_PIPE_HANDLE PipeHandle,
    __in ULONG TransferFlags
    )
{
    m_Urb->TransferFlags = TransferFlags;
    m_Urb->PipeHandle = PipeHandle;
}

USBD_STATUS
FxUsbPipeTransferContext::GetUsbdStatus(
    VOID
    )
{
    return m_Urb->Hdr.Status;
}

FxUsbUrbContext::FxUsbUrbContext(
    VOID
    ) :
    FxUsbRequestContext(FX_RCT_USB_URB_REQUEST),
    m_pUrb(NULL)
{
}

USBD_STATUS
FxUsbUrbContext::GetUsbdStatus(
    VOID
    )
{
    return m_pUrb == NULL ? 0 : m_pUrb->UrbHeader.Status;
}

VOID
FxUsbUrbContext::StoreAndReferenceMemory(
    __in FxRequestBuffer* Buffer
    )
{
    ULONG dummy;

    FxUsbRequestContext::StoreAndReferenceMemory(Buffer);

    //
    // make sure it is framework managed memory or raw PVOID
    //
    ASSERT(Buffer->DataType == FxRequestBufferMemory ||
           Buffer->DataType == FxRequestBufferBuffer);

    Buffer->AssignValues((PVOID*) &m_pUrb, NULL, &dummy);
}

VOID
FxUsbUrbContext::ReleaseAndRestore(
    __in FxRequestBase* Request
    )
{
    m_pUrb = NULL;
    FxUsbRequestContext::ReleaseAndRestore(Request); // __super call
}


FxUsbPipeRequestContext::FxUsbPipeRequestContext(
    __in FX_URB_TYPE FxUrbType
    ) :
    FxUsbRequestContext(FX_RCT_USB_PIPE_REQUEST)
{
    m_USBDHandle = NULL;

    if (FxUrbType == FxUrbTypeLegacy) {
        m_Urb = &m_UrbLegacy;
    }
    else {
        m_Urb = NULL;
    }
}

FxUsbPipeRequestContext::~FxUsbPipeRequestContext(
    VOID
    )
{
    if (m_Urb && (m_Urb != &m_UrbLegacy)) {
        USBD_UrbFree(m_USBDHandle, (PURB)m_Urb);
    }
    m_Urb = NULL;
    m_USBDHandle = NULL;
}

__checkReturn
NTSTATUS
FxUsbPipeRequestContext::AllocateUrb(
    __in USBD_HANDLE USBDHandle
    )
{
    NTSTATUS status;

    if (m_Urb) {
        status = STATUS_INVALID_DEVICE_STATE;
        goto Done;
    }

    status = USBD_UrbAllocate(USBDHandle, (PURB*)&m_Urb);

    if (!NT_SUCCESS(status)) {
        goto Done;
    }

    m_USBDHandle = USBDHandle;

Done:
    return status;
}

VOID
FxUsbPipeRequestContext::Dispose(
    VOID
    )
{
    if (m_Urb && (m_Urb != &m_UrbLegacy)){
        USBD_UrbFree(m_USBDHandle, (PURB) m_Urb);
        m_Urb = NULL;
        m_USBDHandle = NULL;
    }
}

VOID
FxUsbPipeRequestContext::SetInfo(
    __in WDF_USB_REQUEST_TYPE Type,
    __in USBD_PIPE_HANDLE PipeHandle,
    __in USHORT Function
    )
{
    RtlZeroMemory(m_Urb, sizeof(*m_Urb));
    m_Urb->Hdr.Length = sizeof(*m_Urb);
    m_Urb->Hdr.Function = Function;
    m_Urb->PipeHandle = PipeHandle;

    SetUsbType(Type);
}

USBD_STATUS
FxUsbPipeRequestContext::GetUsbdStatus(
    VOID
    )
{
    return m_Urb->Hdr.Status;
}

FxUsbPipe::FxUsbPipe(
    __in PFX_DRIVER_GLOBALS FxDriverGlobals,
    __in FxUsbDevice* UsbDevice
    ) :
    FxIoTarget(FxDriverGlobals, sizeof(FxUsbPipe), FX_TYPE_IO_TARGET_USB_PIPE),
    m_UsbDevice(UsbDevice)
{
    InitializeListHead(&m_ListEntry);
    RtlZeroMemory(&m_PipeInformation, sizeof(m_PipeInformation));
#if (FX_CORE_MODE == FX_CORE_USER_MODE)
    RtlZeroMemory(&m_PipeInformationUm, sizeof(m_PipeInformationUm));
#endif
    m_InterfaceNumber = 0;
    m_Reader = NULL;
    m_UsbInterface = NULL;
    m_CheckPacketSize = TRUE;
    m_USBDHandle = UsbDevice->m_USBDHandle;
    m_UrbType = UsbDevice->m_UrbType;

    MarkNoDeleteDDI(ObjectDoNotLock);
}

VOID
FxUsbPipe::InitPipe(
    __in PUSBD_PIPE_INFORMATION PipeInfo,
    __in UCHAR InterfaceNumber,
    __in FxUsbInterface* UsbInterface
    )
{
    RtlCopyMemory(&m_PipeInformation, PipeInfo, sizeof(m_PipeInformation));
    m_InterfaceNumber = InterfaceNumber;

    if (m_UsbInterface != NULL) {
        m_UsbInterface->RELEASE(this);
        m_UsbInterface = NULL;
    }

    m_UsbInterface = UsbInterface;
    m_UsbInterface->ADDREF(this);
}

FxUsbPipe::~FxUsbPipe()
{
    if (m_UsbInterface != NULL) {
        m_UsbInterface->RemoveDeletedPipe(this);
        m_UsbInterface->RELEASE(this);
    }

    ASSERT(IsListEmpty(&m_ListEntry));
}

BOOLEAN
FxUsbPipe::Dispose()
{
    BOOLEAN callCleanup;

    //
    // Call base class: callbacks, terminates I/Os, etc.
    //
    callCleanup = FxIoTarget::Dispose(); // __super call

    //
    // Don't need the reader anymore. The reader is deleted after calling the
    // parent class Dispose() to preserve the existing deletion order (it was
    // deleted in the Pipe's dtor() before this change).
    //
    if (m_Reader != NULL)
    {
        delete m_Reader;

        //
        // By doing this assignment we prevent misbehaved drivers
        // from crashing the system when they call WdfIoTargetStop from their
        // usb pipe's destroy callback.
        //
        m_Reader = NULL;
    }

    return callCleanup;
}

_Must_inspect_result_
NTSTATUS
FxUsbPipe::GotoStartState(
    __in PLIST_ENTRY    RequestListHead,
    __in BOOLEAN        Lock
    )
{
    NTSTATUS status;
    LONG i;

    if (m_Reader != NULL) {
        if (m_Reader->m_ReadersSubmitted == FALSE) {
            ASSERT(IsListEmpty(&m_SentIoListHead));

            for (i = 0; i < m_Reader->m_NumReaders; i++) {
                FxRequest* pRequest;

                pRequest = m_Reader->m_Readers[i].Request;

                UNREFERENCED_PARAMETER(pRequest); //for fre build
                ASSERT(IsListEmpty(&pRequest->m_ListEntry));
                ASSERT(pRequest->m_DrainSingleEntry.Next == NULL);
            }
        }
    }

    status = FxIoTarget::GotoStartState(RequestListHead, Lock);

    if (m_Reader == NULL || !NT_SUCCESS(status)) {
        return status;
    }

    //
    // Add the repeater requests to the list head so that they are sent by the
    // caller of this function when this function returns IFF they have not yet
    // been queued.  (They can be queued on a start -> start transition.)
    //
    if (m_Reader->m_ReadersSubmitted == FALSE) {
        for (i = 0; i < m_Reader->m_NumReaders; i++) {
            //
            // This will clear ReadCompletedEvent as well
            //
            status = m_Reader->FormatRepeater(&m_Reader->m_Readers[i]);

            if (!NT_SUCCESS(status)) {
                return status;
            }
        }

        //
        // Reset the number of failed readers in case we had failure in a
        // previously started state.
        //
        m_Reader->m_NumFailedReaders = 0;

        for (i = 0; i < m_Reader->m_NumReaders; i++) {
            FxRequest* pRequest;

            pRequest = m_Reader->m_Readers[i].Request;
            pRequest->SetTarget(this);
            pRequest->ADDREF(this);

            //
            // NOTE: This is an elusive backdoor to send the Request down
            // since it is inserted directly into the IoTargets pended list.
            // The IoTarget is not started so we add the request to the
            // pended list so that it is processed when the IoTarget starts.
            //
            m_Reader->m_Pipe->IncrementIoCount();
            InsertTailList(RequestListHead, &pRequest->m_ListEntry);

            //
            // Clear the event only when we know it will be submitted to the
            // target.  It will be set when  the request is submitted to the
            // target and the submit fails or if it is cancelled.
            //
            m_Reader->m_Readers[i].ReadCompletedEvent.Clear();
        }

        m_Reader->m_ReadersSubmitted = TRUE;
    }

    return status;
}

VOID
FxUsbPipe::GotoStopState(
    __in WDF_IO_TARGET_SENT_IO_ACTION   Action,
    __in PSINGLE_LIST_ENTRY             SentRequestListHead,
    __out PBOOLEAN                      Wait,
    __in BOOLEAN                        LockSelf
    )
{
    KIRQL irql;
    PFX_DRIVER_GLOBALS pFxDriverGlobals;

    irql = PASSIVE_LEVEL;
    pFxDriverGlobals = GetDriverGlobals();

    if (LockSelf) {
        Lock(&irql);
    }

    if (m_Reader != NULL) {
        //
        // If we are a continuous reader, always cancel the sent io so that we
        // can resubmit it later on a restart.
        //
        DoTraceLevelMessage(
            pFxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p converting stop action %!WDF_IO_TARGET_SENT_IO_ACTION!"
            " to %!WDF_IO_TARGET_SENT_IO_ACTION!", GetHandle(), Action,
            WdfIoTargetCancelSentIo);

        Action = WdfIoTargetCancelSentIo;
    }

    FxIoTarget::GotoStopState(Action, SentRequestListHead, Wait, FALSE); // __super call

    if (m_Reader != NULL) {
        //
        // The continuous reader requests are no longer enqueued.  Remember that
        // state, so when we restart, we resend them.
        //
        m_Reader->m_ReadersSubmitted = FALSE;

        //
        // Log a message when misbehaved drivers call WdfIoTargetStop
        // from EvtUsbTargetPipeReadersFailed callback.
        //
        if (m_Reader->m_WorkItemThread == Mx::MxGetCurrentThread()) {
            DoTraceLevelMessage(
                pFxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGIOTARGET,
                "WDFUSBPIPE %p is stopped from EvtUsbTargetPipeReadersFailed"
                " callback", GetHandle());

            if (pFxDriverGlobals->IsVerificationEnabled(1, 9, OkForDownLevel)) {
                FxVerifierDbgBreakPoint(pFxDriverGlobals);
            }
        }

        //
        // Do not deadlock when misbehaved drivers (< v1.9) call
        // WdfIoTargetStop from EvtUsbTargetPipeReadersFailed callback.
        //
        if (m_Reader->m_WorkItemThread != Mx::MxGetCurrentThread() ||
            pFxDriverGlobals->IsVersionGreaterThanOrEqualTo(1,9)) {
            //
            // Make sure work item is done. It is possible for the upper class
            // to return wait = false if the list of sent requests is empty. We
            // still want to wait anyway for making sure work item is not about
            // to run or it is running.
            //
            *Wait = TRUE;
        }
    }

    if (LockSelf) {
        Unlock(irql);
    }
}

VOID
FxUsbPipe::GotoPurgeState(
    __in WDF_IO_TARGET_PURGE_IO_ACTION  Action,
    __in PLIST_ENTRY                    PendedRequestListHead,
    __in PSINGLE_LIST_ENTRY             SentRequestListHead,
    __out PBOOLEAN                      Wait,
    __in BOOLEAN                        LockSelf
    )
{
    KIRQL irql;
    PFX_DRIVER_GLOBALS pFxDriverGlobals;

    irql = PASSIVE_LEVEL;
    pFxDriverGlobals = GetDriverGlobals();

    if (LockSelf) {
        Lock(&irql);
    }

    if (m_Reader != NULL) {
        //
        // If we are a continuous reader, always wait for the sent io, so that we
        // can resubmit it later on a restart.
        //
        DoTraceLevelMessage(
            pFxDriverGlobals, TRACE_LEVEL_INFORMATION, TRACINGIOTARGET,
            "WDFUSBPIPE %p converting purge action %!WDF_IO_TARGET_PURGE_IO_ACTION!"
            " to %!WDF_IO_TARGET_PURGE_IO_ACTION!", GetHandle(), Action,
            WdfIoTargetPurgeIoAndWait);

        Action = WdfIoTargetPurgeIoAndWait;
    }

    FxIoTarget::GotoPurgeState(Action, // __super call
                            PendedRequestListHead,
                            SentRequestListHead,
                            Wait,
                            FALSE);

    if (m_Reader != NULL) {
        //
        // The continuous reader requests are no longer enqueued.  Remember that
        // state, so when we restart, we resend them.
        //
        m_Reader->m_ReadersSubmitted = FALSE;

        //
        // Log a message when misbehaved drivers call WdfIoTargetPurge
        // from EvtUsbTargetPipeReadersFailed callback.
        //
        if (m_Reader->m_WorkItemThread == Mx::MxGetCurrentThread()) {
            DoTraceLevelMessage(
                pFxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGIOTARGET,
                "WDFUSBPIPE %p is purged from EvtUsbTargetPipeReadersFailed"
                " callback", GetHandle());

            FxVerifierDbgBreakPoint(pFxDriverGlobals);
        }

        //
        // Make sure work item is done. It is possible for the upper class
        // to return wait = false if the list of sent requests is empty. We
        // still want to wait anyway for making sure work item is not about
        // to run or it is running.
        //
        *Wait = TRUE;
    }

    if (LockSelf) {
        Unlock(irql);
    }
}

VOID
FxUsbPipe::GotoRemoveState(
    __in WDF_IO_TARGET_STATE    NewState,
    __in PLIST_ENTRY            PendedRequestListHead,
    __in PSINGLE_LIST_ENTRY     SentRequestListHead,
    __in BOOLEAN                LockSelf,
    __out PBOOLEAN              Wait
    )
{
    KIRQL irql;

    irql = PASSIVE_LEVEL;

    if (LockSelf) {
        Lock(&irql);
    }

    if (m_Reader != NULL && m_Reader->m_ReadersSubmitted &&
        WdfIoTargetStarted == m_State) {
        //
        // Driver forgot to stop the pipe on D0Exit.
        //
        DoTraceLevelMessage(
            GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGIOTARGET,
            "WDFUSBPIPE %p was not stopped in EvtDeviceD0Exit callback",
            GetHandle());

        if (GetDriverGlobals()->IsVerificationEnabled(1,9,OkForDownLevel)) {
            FxVerifierDbgBreakPoint(GetDriverGlobals());
        }
    }

    FxIoTarget::GotoRemoveState(NewState, // __super call
                             PendedRequestListHead,
                             SentRequestListHead,
                             FALSE,
                             Wait);
    if (m_Reader != NULL) {
        //
        // Make sure work item is done. It is possible for the upper class to
        // return wait = false if the list of sent requests is empty. We still
        // want to wait anyway for making sure work item is not about to run or
        // it is running.
        //
        *Wait = TRUE;
    }

    if (LockSelf) {
        Unlock(irql);
    }
}

VOID
FxUsbPipe::WaitForSentIoToComplete(
    VOID
    )
{
    if (m_Reader != NULL) {
        DoTraceLevelMessage(
            GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGIOTARGET,
            "WDFUSBPIPE %p, waiting for continuous reader work item to complete",
            GetHandle());

        //
        // First, wait for the work item to complete if it is running.
        //
        // NOTE: We don't wait for the DPC  to complete because
        //  they are flushed in FxUsbDevice::Dispose
        //
        m_Reader->m_WorkItem->WaitForExit();

        DoTraceLevelMessage(
            GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGIOTARGET,
            "WDFUSBPIPE %p, cancelling for continuous reader (max of %d)",
            GetHandle(), m_Reader->m_NumReaders);

        //
        // Now that the work item is not running, make sure all the readers are
        // truly canceled and *NOT* in the pended queue.  In between the call to
        // GotoStopState and here, the work item could have run and retried to
        // send the I/O.
        //
        m_Reader->CancelRepeaters();
    }

    DoTraceLevelMessage(
        GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGIOTARGET,
        "WDFUSBPIPE %p, waiting for all i/o to complete", GetHandle());

    //
    // Finally, let the parent class wait for all I/O to complete
    //
    FxIoTarget::WaitForSentIoToComplete(); // __super call
}

_Must_inspect_result_
NTSTATUS
FxUsbPipe::InitContinuousReader(
    __in PWDF_USB_CONTINUOUS_READER_CONFIG Config,
    __in size_t TotalBufferLength
    )
{
    FxUsbPipeContinuousReader* pReader;
    NTSTATUS status;
    UCHAR numReaders;

    pReader = NULL;

    if (m_Reader != NULL) {
        status = STATUS_INVALID_DEVICE_STATE;

        DoTraceLevelMessage(
            GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGIOTARGET,
            "Continuous reader already initialized on WDFUSBPIPE %p %!STATUS!",
            GetHandle(), status);

        return status;
    }

    numReaders = Config->NumPendingReads;

    if (numReaders == 0) {
        numReaders = NUM_PENDING_READS_DEFAULT;
    }
    else if (numReaders > NUM_PENDING_READS_MAX) {
        numReaders = NUM_PENDING_READS_MAX;
    }

    pReader = new(GetDriverGlobals(), numReaders)
        FxUsbPipeContinuousReader(this, numReaders);

    if (pReader == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Allocate all of the structurs and objects required
    //
    status = pReader->Config(Config, TotalBufferLength);

    if (!NT_SUCCESS(status)) {
        delete pReader;
        return status;
    }

    pReader->m_ReadCompleteCallback = Config->EvtUsbTargetPipeReadComplete;
    pReader->m_ReadCompleteContext = Config->EvtUsbTargetPipeReadCompleteContext;

    pReader->m_ReadersFailedCallback = Config->EvtUsbTargetPipeReadersFailed;

    if (InterlockedCompareExchangePointer((PVOID*) &m_Reader,
                                          pReader,
                                          NULL) == NULL) {
        //
        // We set the field, do nothing.
        //
        DO_NOTHING();
    }
    else {
        //
        // Some other thread came in and set the field, free our allocation.
        //
        delete pReader;
    }

    return STATUS_SUCCESS;
}

_Must_inspect_result_
NTSTATUS
FxUsbPipe::_FormatTransfer(
    __in  PFX_DRIVER_GLOBALS FxDriverGlobals,
    __in WDFUSBPIPE Pipe,
    __in WDFREQUEST Request,
    __in_opt WDFMEMORY TransferMemory,
    __in_opt PWDFMEMORY_OFFSET TransferOffsets,
    __in ULONG Flags
    )
{
    FxRequestBuffer buf;
    IFxMemory* pMemory;
    FxUsbPipe* pUsbPipe;
    FxRequest* pRequest;
    NTSTATUS status;

    FxObjectHandleGetPtrAndGlobals(FxDriverGlobals,
                                   Pipe,
                                   FX_TYPE_IO_TARGET_USB_PIPE,
                                   (PVOID*) &pUsbPipe,
                                   &FxDriverGlobals);

    FxObjectHandleGetPtr(FxDriverGlobals,
                         Request,
                         FX_TYPE_REQUEST,
                         (PVOID*) &pRequest);

    //
    // We allow zero length transfers (which are indicated by TransferMemory == NULL)
    //
    if (TransferMemory != NULL) {
        FxObjectHandleGetPtr(FxDriverGlobals,
                             TransferMemory,
                             IFX_TYPE_MEMORY,
                             (PVOID*) &pMemory);

        status = pMemory->ValidateMemoryOffsets(TransferOffsets);
        if (!NT_SUCCESS(status)) {
            goto Done;
        }

        buf.SetMemory(pMemory, TransferOffsets);
    }
    else {
        pMemory = NULL;
    }

    status = pUsbPipe->FormatTransferRequest(pRequest,  &buf, Flags);

    if (NT_SUCCESS(status)) {
        FxUsbPipeTransferContext* pContext;

        pContext = (FxUsbPipeTransferContext*) pRequest->GetContext();

        //
        // By assuming the fields are at the same offset, we can use simpler
        // logic (w/out comparisons for type) to set them.
        //
        WDFCASSERT(
            FIELD_OFFSET(WDF_USB_REQUEST_COMPLETION_PARAMS, Parameters.PipeWrite.Buffer) ==
            FIELD_OFFSET(WDF_USB_REQUEST_COMPLETION_PARAMS, Parameters.PipeRead.Buffer)
            );

        WDFCASSERT(
            FIELD_OFFSET(WDF_USB_REQUEST_COMPLETION_PARAMS, Parameters.PipeWrite.Offset) ==
            FIELD_OFFSET(WDF_USB_REQUEST_COMPLETION_PARAMS, Parameters.PipeRead.Offset)
            );

        pContext->m_UsbParameters.Parameters.PipeWrite.Buffer = TransferMemory;
        pContext->m_UsbParameters.Parameters.PipeWrite.Length = buf.GetBufferLength();

        pContext->m_UsbParameters.Parameters.PipeWrite.Offset =
            (TransferOffsets != NULL) ? TransferOffsets->BufferOffset
                                      : 0;
        pContext->SetUsbType(
            (Flags & USBD_TRANSFER_DIRECTION_IN) ? WdfUsbRequestTypePipeRead
                                                 : WdfUsbRequestTypePipeWrite
            );
    }

Done:
    DoTraceLevelMessage(FxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGIOTARGET,
                        "WDFUSBPIPE %p, WDFREQUEST %p, WDFMEMORY %p, %!STATUS!",
                        Pipe, Request, TransferMemory, status);

    return status;
}

_Must_inspect_result_
NTSTATUS
FxUsbPipe::_SendTransfer(
    __in PFX_DRIVER_GLOBALS FxDriverGlobals,
    __in WDFUSBPIPE Pipe,
    __in_opt WDFREQUEST Request,
    __in_opt PWDF_REQUEST_SEND_OPTIONS RequestOptions,
    __in_opt PWDF_MEMORY_DESCRIPTOR MemoryDescriptor,
    __out_opt PULONG BytesTransferred,
    __in ULONG Flags
    )
{
    FxRequestBuffer buf;
    FxUsbPipe* pUsbPipe;
    NTSTATUS status;

    FxObjectHandleGetPtrAndGlobals(FxDriverGlobals,
                                   Pipe,
                                   FX_TYPE_IO_TARGET_USB_PIPE,
                                   (PVOID*) &pUsbPipe,
                                   &FxDriverGlobals);

    FxUsbPipeTransferContext context(FxUrbTypeLegacy);

    FxSyncRequest request(FxDriverGlobals, &context, Request);

    //
    // FxSyncRequest always succeesds for KM but can fail for UM.
    //
    status = request.Initialize();
    if (!NT_SUCCESS(status)) {
        DoTraceLevelMessage(FxDriverGlobals, TRACE_LEVEL_ERROR, TRACINGIOTARGET,
                            "Failed to initialize FxSyncRequest");
        return status;
    }

    if (BytesTransferred != NULL) {
        *BytesTransferred = 0;
    }

    status = FxVerifierCheckIrqlLevel(FxDriverGlobals, PASSIVE_LEVEL);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    status = FxValidateRequestOptions(FxDriverGlobals, RequestOptions);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    //
    // We allow zero length writes (which are indicated by MemoryDescriptor == NULL)
    //
    if (MemoryDescriptor != NULL) {
        status = buf.ValidateMemoryDescriptor(FxDriverGlobals, MemoryDescriptor);
        if (!NT_SUCCESS(status)) {
            return status;
        }
    }

    status = pUsbPipe->FormatTransferRequest(request.m_TrueRequest, &buf, Flags);

    if (NT_SUCCESS(status)) {
        DoTraceLevelMessage(
            FxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGIOTARGET,
            "WDFUSBPIPE %p, WDFREQUEST %p being submitted",
            Pipe, request.m_TrueRequest->GetTraceObjectHandle());

        status = pUsbPipe->SubmitSync(request.m_TrueRequest, RequestOptions);

        //
        // Even on error we want to set this value.  USBD should be clearing
        // it if the transfer fails.
        //
        if (BytesTransferred != NULL) {
            *BytesTransferred = context.GetUrbTransferLength();
        }
    }

    DoTraceLevelMessage(FxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGIOTARGET,
                        "WDFUSBPIPE %p, %!STATUS!", Pipe, status);

    return status;
}

_Must_inspect_result_
NTSTATUS
FxUsbPipe::FormatAbortRequest(
    __in FxRequestBase* Request
    )
{
    FxUsbPipeRequestContext* pContext;
    NTSTATUS status;
    FX_URB_TYPE urbType;

    status = Request->ValidateTarget(this);
    if (!NT_SUCCESS(status)) {
        DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGIOTARGET,
                            "Pipe %p, Request %p, setting target failed, "
                            "status %!STATUS!", this, Request, status);

        return status;
    }

    if (Request->HasContextType(FX_RCT_USB_PIPE_REQUEST)) {
        pContext = (FxUsbPipeRequestContext*) Request->GetContext();
    }
    else {

        urbType = m_UsbDevice->GetFxUrbTypeForRequest(Request);
        pContext = new(GetDriverGlobals()) FxUsbPipeRequestContext(urbType);
        if (pContext == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
        if (urbType == FxUrbTypeUsbdAllocated) {
            status = pContext->AllocateUrb(m_USBDHandle);
            if (!NT_SUCCESS(status)) {
                delete pContext;
                return status;
            }
            //
            // Since the AllocateUrb routine calls USBD_xxxUrbAllocate APIs to allocate an Urb, it is
            // important to release those resorces before the devnode is removed. Those
            // resoruces are removed at the time Request is disposed.
            //
            Request->EnableContextDisposeNotification();
        }
#endif

        Request->SetContext(pContext);
    }

#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
    pContext->SetInfo(WdfUsbRequestTypePipeAbort,
                      m_PipeInformation.PipeHandle,
                      URB_FUNCTION_ABORT_PIPE);

    if (pContext->m_Urb == &pContext->m_UrbLegacy) {
        urbType = FxUrbTypeLegacy;
    }
    else {
        urbType = FxUrbTypeUsbdAllocated;
    }

    FxFormatUsbRequest(Request, (PURB)pContext->m_Urb, urbType, m_USBDHandle);
#elif (FX_CORE_MODE == FX_CORE_USER_MODE)
    pContext->SetInfo(WdfUsbRequestTypePipeAbort,
                      m_UsbInterface->m_WinUsbHandle,
                      m_PipeInformationUm.PipeId,
                      UMURB_FUNCTION_ABORT_PIPE);
    FxUsbUmFormatRequest(Request, &pContext->m_UmUrb.UmUrbPipeRequest.Hdr, m_UsbDevice->m_pHostTargetFile);
#endif

    return STATUS_SUCCESS;
}

_Must_inspect_result_
NTSTATUS
FxUsbPipe::FormatResetRequest(
    __in FxRequestBase* Request
    )
{
    FxUsbPipeRequestContext* pContext;
    NTSTATUS status;
    FX_URB_TYPE urbType;

    status = Request->ValidateTarget(this);
    if (!NT_SUCCESS(status)) {
        DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGIOTARGET,
                            "Pipe %p, Request %p, setting target failed, "
                            "status %!STATUS!", this, Request, status);

        return status;
    }

    if (Request->HasContextType(FX_RCT_USB_PIPE_REQUEST)) {
        pContext = (FxUsbPipeRequestContext*) Request->GetContext();
    }
    else {
        urbType = m_UsbDevice->GetFxUrbTypeForRequest(Request);
        pContext = new(GetDriverGlobals()) FxUsbPipeRequestContext(urbType);
        if (pContext == NULL) {
            return STATUS_INSUFFICIENT_RESOURCES;
        }

#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
        if (urbType == FxUrbTypeUsbdAllocated) {
            status = pContext->AllocateUrb(m_USBDHandle);
            if (!NT_SUCCESS(status)) {
                delete pContext;
                return status;
            }
            //
            // Since the AllocateUrb routine calls USBD_xxxUrbAllocate APIs to allocate an Urb, it is
            // important to release those resorces before the devnode is removed. Those
            // resoruces are removed at the time Request is disposed.
            //
            Request->EnableContextDisposeNotification();
        }
#endif

        Request->SetContext(pContext);
    }

#if (FX_CORE_MODE == FX_CORE_KERNEL_MODE)
    //
    // URB_FUNCTION_RESET_PIPE and URB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL
    // are the same value
    //
    pContext->SetInfo(WdfUsbRequestTypePipeReset,
                      m_PipeInformation.PipeHandle,
                      URB_FUNCTION_RESET_PIPE);

    if (pContext->m_Urb == &pContext->m_UrbLegacy) {
        urbType = FxUrbTypeLegacy;
    }
    else {
        urbType = FxUrbTypeUsbdAllocated;
    }

    FxFormatUsbRequest(Request, (PURB)pContext->m_Urb, urbType, m_USBDHandle);
#elif (FX_CORE_MODE == FX_CORE_USER_MODE)
    pContext->SetInfo(WdfUsbRequestTypePipeReset,
                      m_UsbInterface->m_WinUsbHandle,
                      m_PipeInformationUm.PipeId,
                      UMURB_FUNCTION_SYNC_RESET_PIPE_AND_CLEAR_STALL);
    FxUsbUmFormatRequest(Request, &pContext->m_UmUrb.UmUrbPipeRequest.Hdr, m_UsbDevice->m_pHostTargetFile);
#endif

    return STATUS_SUCCESS;
}

NTSTATUS
FxUsbPipe::Reset(
    VOID
    )
{
    FxUsbPipeRequestContext context(FxUrbTypeLegacy);

    FxSyncRequest request(GetDriverGlobals(), &context);
    NTSTATUS status;

    //
    // FxSyncRequest always succeesds for KM but can fail for UM.
    //
    status = request.Initialize();
    if (!NT_SUCCESS(status)) {
        DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGIOTARGET,
                            "Failed to initialize FxSyncRequest");
        return status;
    }

    status = FormatResetRequest(request.m_TrueRequest);
    if (NT_SUCCESS(status)) {
        if (m_Reader != NULL) {
            //
            // This assumes that no other I/O besides reader I/O is going on.
            //
            m_Reader->CancelRepeaters();
        }
        else {
             CancelSentIo();
        }
        status = SubmitSyncRequestIgnoreTargetState(request.m_TrueRequest, NULL);
    }
    return status;
}