reactos/sdk/lib/drivers/wdf/kmdf/inc/private/fxdmatransaction.hpp

1564 lines
44 KiB
C++
Raw Normal View History

//
// Copyright (C) Microsoft. All rights reserved.
//
#ifndef _FXDMATRANSACTION_HPP_
#define _FXDMATRANSACTION_HPP_
extern "C" {
// #include "FxDmaTransaction.hpp.tmh"
}
#include "fxdmatransactioncallbacks.hpp"
//
// This type is used to allocate scatter-gather list of 1 element on the stack.
//
typedef DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) UCHAR UCHAR_MEMORY_ALIGNED;
// begin_wpp enum
//
// FxDmaTransactionStateCreated when the object is created.
// FxDmaTransactionStateInitialized when object is initialized using with
// Mdl/VA/Length.
// FxDmaTransactionStateReserved when driver calls AllocateResources until
// the adapter control routine returns
// FxDmaTransactionStateTransfer is called when the driver call Execute
// to start the DMA transfer.
// FxDmaTransactionStateTransferCompleted is when transfer is completed or
// aborted
// FxDmaTransactionStateTransferFailed is set if the framework is not able
// to start the transfer due to error.
// FxDmaTransactionStateReleased is set when the object is reinitailized for reuse
// FxDmaTransactionStateDeleted is set in the Dipose due to WdfObjectDelete
//
enum FxDmaTransactionState {
FxDmaTransactionStateInvalid = 0,
FxDmaTransactionStateCreated,
FxDmaTransactionStateReserved,
FxDmaTransactionStateInitialized,
FxDmaTransactionStateTransfer,
FxDmaTransactionStateTransferCompleted,
FxDmaTransactionStateTransferFailed,
FxDmaTransactionStateReleased,
FxDmaTransactionStateDeleted,
};
//
// FxDmaCompletionTypeFull is used when the driver calls WdfDmaTransactionDmaComplete
// to indicate that last framgement has been transmitted fully and to initiate
// the transfer of next fragment.
// FxDmaCompletionTypePartial is used when the driver completes the transfer and
// specifies a amount of bytes it has transfered, and to initiate the next transfer
// from the untransmitted portion of the buffer.
// FxDmaCompletionTypeAbort i used when the driver calls DmaCompleteFinal to indicate
// that's the final transfer and not initiate anymore transfers for the remaining
// data.
//
enum FxDmaCompletionType {
FxDmaCompletionTypeFull = 1,
FxDmaCompletionTypePartial,
FxDmaCompletionTypeAbort,
};
// end_wpp
//
// This tag is used to track whether the request pointer in the transaction
// has a reference taken on it. Since the pointer is guaranteed to be
// 4-byte aligned this can be set and cleared without destroying the pointer.
//
#define FX_STRONG_REF_TAG 0x1
//
// Simple set of macros to encode and decode tagged pointers.
//
#define FX_ENCODE_POINTER(T,p,tag) ((T*) ((ULONG_PTR) p | (ULONG_PTR) tag))
#define FX_DECODE_POINTER(T,p,tag) ((T*) ((ULONG_PTR) p & ~(ULONG_PTR) tag))
#define FX_IS_POINTER_ENCODED(p,tag) ((((ULONG_PTR) p & (ULONG_PTR) tag) == tag) ? TRUE : FALSE)
//
// An uninitialized value for Dma completion status
//
#define UNDEFINED_DMA_COMPLETION_STATUS ((DMA_COMPLETION_STATUS) -1)
class FxDmaTransactionBase : public FxNonPagedObject {
friend class FxDmaEnabler;
public:
FxDmaTransactionBase(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in USHORT ObjectSize,
__in USHORT ExtraSize,
__in FxDmaEnabler *DmaEnabler
);
static
VOID
_ComputeNextTransferAddress(
__in PMDL CurrentMdl,
__in size_t CurrentOffset,
__in ULONG Transferred,
__deref_out PMDL *NextMdl,
__out size_t *NextOffset
);
_Must_inspect_result_
static
NTSTATUS
_CalculateRequiredMapRegisters(
__in PMDL Mdl,
__in size_t CurrentOffset,
__in ULONG Length,
__in ULONG AvailableMapRegisters,
__out_opt PULONG PossibleTransferLength,
__out PULONG MapRegistersRequired
);
virtual
BOOLEAN
Dispose(
VOID
);
_Must_inspect_result_
NTSTATUS
Initialize(
__in PFN_WDF_PROGRAM_DMA ProgramDmaFunction,
__in WDF_DMA_DIRECTION DmaDirection,
__in PMDL Mdl,
__in size_t Offset,
__in ULONG Length
);
_Must_inspect_result_
virtual
NTSTATUS
InitializeResources(
VOID
)=0;
_Must_inspect_result_
NTSTATUS
Execute(
__in PVOID Context
);
_Must_inspect_result_
virtual
NTSTATUS
StartTransfer(
VOID
)=0;
_Must_inspect_result_
virtual
NTSTATUS
StageTransfer(
VOID
)=0;
BOOLEAN
DmaCompleted(
__in size_t TransferredLength,
__out NTSTATUS * ReturnStatus,
__in FxDmaCompletionType CompletionType
);
_Must_inspect_result_
virtual
NTSTATUS
TransferCompleted(
VOID
)=0;
VOID
ReleaseForReuse(
__in BOOLEAN ForceRelease
);
virtual
VOID
ReleaseResources(
__in BOOLEAN ForceRelease
)=0;
VOID
GetTransferInfo(
__out_opt ULONG *MapRegisterCount,
__out_opt ULONG *ScatterGatherElementCount
);
__forceinline
size_t
GetBytesTransferred(
VOID
)
{
return m_Transferred;
}
__forceinline
FxDmaEnabler *
GetDmaEnabler(
VOID
)
{
return m_DmaEnabler;
}
__forceinline
FxRequest *
GetRequest(
VOID
)
{
//
// Strip out the strong reference tag if it's set
//
return FX_DECODE_POINTER(FxRequest,
m_EncodedRequest,
FX_STRONG_REF_TAG);
}
__forceinline
BOOLEAN
IsRequestReferenced(
VOID
)
{
return FX_IS_POINTER_ENCODED(m_EncodedRequest, FX_STRONG_REF_TAG);
}
__forceinline
VOID
SetRequest(
__in FxRequest* Request
)
{
ASSERT(m_EncodedRequest == NULL);
//
// Make sure the pointer doesn't have the strong ref flag set already
//
ASSERT(FX_IS_POINTER_ENCODED(Request, FX_STRONG_REF_TAG) == FALSE);
m_EncodedRequest = Request;
}
__forceinline
VOID
ReferenceRequest(
VOID
)
{
ASSERT(m_EncodedRequest != NULL);
ASSERT(IsRequestReferenced() == false);
//
// Take a reference on the irp to catch completion of request
// when there is a pending DMA transaction.
// While there is no need to take a reference on request itself,
// I'm keeping it to avoid regression as we are so close to
// shipping this.
//
m_EncodedRequest->AddIrpReference();
//
// Increment reference to this Request.
// See complementary section in WdfDmaTransactionDelete
// and WdfDmaTransactionRelease.
//
m_EncodedRequest->ADDREF( this );
m_EncodedRequest = FX_ENCODE_POINTER(FxRequest,
m_EncodedRequest,
FX_STRONG_REF_TAG);
}
__forceinline
VOID
ReleaseButRetainRequest(
VOID
)
{
ASSERT(m_EncodedRequest != NULL);
ASSERT(IsRequestReferenced());
//
// Clear the referenced bit on the encoded request.
//
m_EncodedRequest = FX_DECODE_POINTER(FxRequest,
m_EncodedRequest,
FX_STRONG_REF_TAG);
//
// Release this reference to the Irp and FxRequest.
//
m_EncodedRequest->ReleaseIrpReference();
m_EncodedRequest->RELEASE( this );
}
__forceinline
VOID
ClearRequest(
VOID
)
{
if (IsRequestReferenced()) {
ReleaseButRetainRequest();
}
m_EncodedRequest = NULL;
}
__forceinline
size_t
GetMaximumFragmentLength(
VOID
)
{
return m_MaxFragmentLength;
}
__forceinline
VOID
SetMaximumFragmentLength(
size_t MaximumFragmentLength
)
{
if (MaximumFragmentLength < m_AdapterInfo->MaximumFragmentLength) {
m_MaxFragmentLength = MaximumFragmentLength;
}
}
__forceinline
size_t
GetCurrentFragmentLength(
VOID
)
{
return m_CurrentFragmentLength;
}
__forceinline
WDFDMATRANSACTION
GetHandle(
VOID
)
{
return (WDFDMATRANSACTION) GetObjectHandle();
}
PVOID
GetTransferContext(
VOID
)
{
return m_TransferContext;
}
VOID
SetImmediateExecution(
__in BOOLEAN Value
);
BOOLEAN
CancelResourceAllocation(
VOID
);
FxDmaTransactionState
GetTransactionState(
VOID
)
{
return m_State;
}
protected:
FxDmaTransactionState m_State;
WDF_DMA_DIRECTION m_DmaDirection;
FxDmaEnabler* m_DmaEnabler;
//
// Depending on the direction of the transfer, this one
// points to either m_ReadAdapterInfo or m_WriteAdapterInfo
// structure of the DMA enabler.
//
FxDmaDescription* m_AdapterInfo;
//
// Request associated with this transfer. Encoding uses the
// FX_[EN|DE]CODE_POINTER macros with the FX_STRONG_REF_TAG
// to indicate whether the reference has been taken or not
//
FxRequest* m_EncodedRequest;
//
// Callback and context for ProgramDma function
//
// The callback is overloaded to also hold the callback for
// Packet & System transfer's Reserve callback (to save space.)
// This is possible because the driver may not call execute
// and reserve in parallel on the same transaction. Disambiguate
// using the state of the transaction.
//
FxDmaTransactionProgramOrReserveDma m_DmaAcquiredFunction;
PVOID m_DmaAcquiredContext;
//
// The DMA transfer context (when using V3 DMA)
//
PVOID m_TransferContext;
//
// This is the first MDL of the transaction.
//
PMDL m_StartMdl;
//
// This is the MDL where the current transfer is being executed.
// If the data spans multiple MDL then this would be different
// from the startMDL when we stage large transfers and also
// if the driver performs partial transfers.
//
PMDL m_CurrentFragmentMdl;
//
// Starting offset in the first m_StartMdl. This might be same as
// m_StartMdl->StartVA.
//
size_t m_StartOffset;
//
// Points to address where the next transfer will begin.
//
size_t m_CurrentFragmentOffset;
//
// This is maximum length of transfer that can be made. This is
// computed based on the available map registers and driver
// configuration.
//
size_t m_MaxFragmentLength;
//
// Length of the whole transaction.
//
size_t m_TransactionLength;
//
// Number of bytes pending to be transfered.
//
size_t m_Remaining;
//
// Total number of bytes transfered.
//
size_t m_Transferred;
//
// Number of bytes transfered in the last transaction.
//
size_t m_CurrentFragmentLength;
//
// DMA flags for passing to GetScatterGatherListEx &
// AllocateAdapterChannelEx
//
ULONG m_Flags;
static
PVOID
GetStartVaFromOffset(
__in PMDL Mdl,
__in size_t Offset
)
{
return ((PUCHAR) MmGetMdlVirtualAddress(Mdl)) + Offset;
}
virtual
VOID
Reuse(
VOID
)
{
return;
}
};
class FxDmaScatterGatherTransaction : public FxDmaTransactionBase {
public:
FxDmaScatterGatherTransaction(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in USHORT ExtraSize,
__in FxDmaEnabler *DmaEnabler
);
virtual
BOOLEAN
Dispose(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
InitializeResources(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
StartTransfer(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
TransferCompleted(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
StageTransfer(
VOID
);
virtual
VOID
ReleaseResources(
__in BOOLEAN ForceRelease
);
_Must_inspect_result_
static
NTSTATUS
_Create(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in PWDF_OBJECT_ATTRIBUTES Attributes,
__in FxDmaEnabler* DmaEnabler,
__out WDFDMATRANSACTION* Transaction
);
protected:
//
// Scatter-Gather list provided by the system.
//
PSCATTER_GATHER_LIST m_SGList;
//
// Preallocated memory from lookaside buffer for the
// scatter-gather list when running on XP and later OSes.
//
PVOID m_LookasideBuffer;
private:
static
VOID
_AdapterListControl(
__in DEVICE_OBJECT * DeviceObject,
__in IRP * Irp,
__in SCATTER_GATHER_LIST * SgList,
__in VOID * Context
);
_Must_inspect_result_
NTSTATUS
GetScatterGatherList (
__in PMDL Mdl,
__in size_t CurrentOffset,
__in ULONG Length,
__in PDRIVER_LIST_CONTROL ExecutionRoutine,
__in PVOID Context
)
{
NTSTATUS status;
KIRQL irql;
KeRaiseIrql(DISPATCH_LEVEL, &irql);
if (m_DmaEnabler->UsesDmaV3())
{
PDMA_OPERATIONS dmaOperations =
m_AdapterInfo->AdapterObject->DmaOperations;
status = dmaOperations->GetScatterGatherListEx(
m_AdapterInfo->AdapterObject,
m_DmaEnabler->m_FDO,
GetTransferContext(),
Mdl,
CurrentOffset,
Length,
m_Flags,
ExecutionRoutine,
Context,
(BOOLEAN) m_DmaDirection,
NULL,
NULL,
NULL
);
}
else
{
status = m_AdapterInfo->AdapterObject->DmaOperations->
GetScatterGatherList(m_AdapterInfo->AdapterObject,
m_DmaEnabler->m_FDO,
Mdl,
GetStartVaFromOffset(Mdl, CurrentOffset),
Length,
ExecutionRoutine,
Context,
(BOOLEAN) m_DmaDirection);
}
KeLowerIrql(irql);
return status;
}
VOID
PutScatterGatherList(
__in PSCATTER_GATHER_LIST ScatterGather
)
{
KIRQL irql;
KeRaiseIrql(DISPATCH_LEVEL, &irql);
m_AdapterInfo->AdapterObject->DmaOperations->
PutScatterGatherList(m_AdapterInfo->AdapterObject,
ScatterGather,
(BOOLEAN) m_DmaDirection);
KeLowerIrql(irql);
return;
}
_Must_inspect_result_
NTSTATUS
BuildScatterGatherList(
__in PMDL Mdl,
__in size_t CurrentOffset,
__in ULONG Length,
__in PDRIVER_LIST_CONTROL ExecutionRoutine,
__in PVOID Context,
__in PVOID ScatterGatherBuffer,
__in ULONG ScatterGatherBufferLength
)
{
NTSTATUS status;
KIRQL irql;
KeRaiseIrql(DISPATCH_LEVEL, &irql);
if (m_DmaEnabler->UsesDmaV3()) {
PDMA_OPERATIONS dmaOperations =
m_AdapterInfo->AdapterObject->DmaOperations;
ULONG flags = 0;
if (GetDriverGlobals()->IsVersionGreaterThanOrEqualTo(1,15)) {
//
// Though the correct behavior is to pass the m_Flags to the
// BuildScatterGatherListEx function, the code was not doing it
// for versions <= 1.13. To reduce any chance of regression,
// the m_Flags is honored for drivers that are 1.15
// or newer.
//
flags = m_Flags;
}
status = dmaOperations->BuildScatterGatherListEx(
m_AdapterInfo->AdapterObject,
m_DmaEnabler->m_FDO,
GetTransferContext(),
Mdl,
CurrentOffset,
Length,
flags,
ExecutionRoutine,
Context,
(BOOLEAN) m_DmaDirection,
ScatterGatherBuffer,
ScatterGatherBufferLength,
NULL,
NULL,
NULL
);
}
else {
status = m_AdapterInfo->AdapterObject->DmaOperations->
BuildScatterGatherList(m_AdapterInfo->AdapterObject,
m_DmaEnabler->m_FDO,
Mdl,
GetStartVaFromOffset(Mdl, CurrentOffset),
Length,
ExecutionRoutine,
Context,
(BOOLEAN) m_DmaDirection,
ScatterGatherBuffer,
ScatterGatherBufferLength);
}
KeLowerIrql(irql);
return status;
}
};
class FxDmaPacketTransaction : public FxDmaTransactionBase {
protected:
FxDmaPacketTransaction(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in USHORT ObjectSize,
__in USHORT ExtraSize,
__in FxDmaEnabler *DmaEnabler
);
public:
_Must_inspect_result_
virtual
NTSTATUS
InitializeResources(
VOID
);
_Must_inspect_result_
NTSTATUS
ReserveAdapter(
__in ULONG NumberOfMapRegisters,
__in WDF_DMA_DIRECTION Direction,
__in PFN_WDF_RESERVE_DMA Callback,
__in_opt PVOID Context
);
VOID
ReleaseAdapter(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
StartTransfer(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
TransferCompleted(
VOID
);
_Must_inspect_result_
virtual
NTSTATUS
StageTransfer(
VOID
);
virtual
VOID
ReleaseResources(
__in BOOLEAN ForceRelease
);
_Must_inspect_result_
static
NTSTATUS
_Create(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in PWDF_OBJECT_ATTRIBUTES Attributes,
__in FxDmaEnabler* DmaEnabler,
__out WDFDMATRANSACTION* Transaction
);
VOID
SetDeviceAddressOffset(
__in ULONG Offset
)
{
m_DeviceAddressOffset = Offset;
}
protected:
//
// Number of map registers to be used in this transfer.
// This value is the least of the number of map registers
// needed to satisfy the current transfer request, and the
// number of available map registers returned by IoGetDmaAdapter.
//
ULONG m_MapRegistersNeeded;
//
// Opaque-value represents the map registers that the system has
// assigned for this transfer operation. We pass this value in
// FlushAdapterBuffers, FreeMapRegisters, and MapTransfer.
//
PVOID m_MapRegisterBase;
//
// TRUE when the map register base above is valid. The HAL can give
// us a NULL map register base when double buffering isn't required,
// so we can't just do a NULL test on m_MapRegisterBase to know if
// the DMA channel is allocated.
//
BOOLEAN m_MapRegisterBaseSet;
//
// For system-DMA this provides the offset from the original
// DeviceAddress used to compute the device register to or from which
// DMA should occur.
//
ULONG m_DeviceAddressOffset;
//
// 0 if the transaction has not reserved the enabler. Otherwise
// this is the number of map registers requested for the reservation.
// This value persists across a reuse and reinitialization of the
// transaction, and is only cleared when the enabler is released.
//
ULONG m_MapRegistersReserved;
//
// These fields are used to defer completion or staging while another
// thread/CPU is already staging. They are protected by the
// transfer state lock.
//
// These values are cleared when checked, not in InitializeResources.
// It's possible for a transaction being staged to complete on another
// CPU, get reused and reinitialized. Clearing these values in
// InitializeResources would destroy the state the prior call to
// StageTransfer depends on.
//
struct {
//
// Non-null when a staging operation is in progress on some CPU.
// When set any attempt to call the DMA completion routine or
// stage the transfer again (due to a call to TransferComplete)
// will be deferred to this thread.
//
PKTHREAD CurrentStagingThread;
//
// Indicates that a nested or concurrent attempt to stage
// the transaction was deferred. The CurrentStagingThread
// will restage the transaction.
//
BOOLEAN RerunStaging;
//
// Indicates that a nested or concurrent attempt to call the
// DMA completion routine occurred. The CurrentStagingThread
// will call the DMA completion routine when it unwinds providing
// the saved CompletionStatus
//
BOOLEAN RerunCompletion;
DMA_COMPLETION_STATUS CompletionStatus;
} m_TransferState;
//
// Indicates that the DMA transfer has been cancelled.
// Set during StopTransfer and cleared during InitializeResources
// Checked during StageTransfer. Stop and Initialize should never
// race (it's not valid for the driver to stop an uninitialized
// transaction). Stop and Stage can race but in that case Stop
// will mark the transaction context such that MapTransfer fails
// (and that's protected by a HAL lock).
//
// So this field does not need to be volatile or interlocked, but
// it needs to be sized to allow atomic writes.
//
ULONG m_IsCancelled;
virtual
VOID
Reuse(
VOID
)
{
return;
}
protected:
inline
void
SetMapRegisterBase(
__in PVOID Value
)
{
NT_ASSERTMSG("Map register base is already set",
m_MapRegisterBaseSet == FALSE);
m_MapRegisterBase = Value;
m_MapRegisterBaseSet = TRUE;
}
inline
void
ClearMapRegisterBase(
VOID
)
{
NT_ASSERTMSG("Map register base was not set",
m_MapRegisterBaseSet == TRUE);
m_MapRegisterBaseSet = FALSE;
}
inline
BOOLEAN
IsMapRegisterBaseSet(
VOID
)
{
return m_MapRegisterBaseSet;
}
inline
PVOID
GetMapRegisterBase(
VOID
)
{
NT_ASSERTMSG("Map register base is not set",
m_MapRegisterBaseSet == TRUE);
return m_MapRegisterBase;
}
virtual
IO_ALLOCATION_ACTION
GetAdapterControlReturnValue(
VOID
)
{
return DeallocateObjectKeepRegisters;
}
virtual
BOOLEAN
PreMapTransfer(
VOID
)
{
return TRUE;
}
_Acquires_lock_(this)
VOID
__drv_raisesIRQL(DISPATCH_LEVEL)
#pragma prefast(suppress:__WARNING_FAILING_TO_ACQUIRE_MEDIUM_CONFIDENCE, "transferring lock name to 'this->TransferStateLock'")
#pragma prefast(suppress:__WARNING_FAILING_TO_RELEASE_MEDIUM_CONFIDENCE, "transferring lock name to 'this->TransferStateLock'")
LockTransferState(
__out __drv_deref(__drv_savesIRQL) KIRQL *OldIrql
)
{
Lock(OldIrql);
}
_Requires_lock_held_(this)
_Releases_lock_(this)
VOID
#pragma prefast(suppress:__WARNING_FAILING_TO_RELEASE_MEDIUM_CONFIDENCE, "transferring lock name from 'this->TransferStateLock'")
UnlockTransferState(
__in __drv_restoresIRQL KIRQL OldIrql
)
{
#pragma prefast(suppress:__WARNING_CALLER_FAILING_TO_HOLD, "transferring lock name from 'this->TransferStateLock'")
Unlock(OldIrql);
}
virtual
PDMA_COMPLETION_ROUTINE
GetTransferCompletionRoutine(
VOID
)
{
return NULL;
}
static
IO_ALLOCATION_ACTION
STDCALL
_AdapterControl(
__in PDEVICE_OBJECT DeviceObject,
__in PIRP Irp,
__in PVOID MapRegisterBase,
__in PVOID Context
);
_Must_inspect_result_
NTSTATUS
AcquireDevice(
VOID
)
{
if (m_DmaEnabler->UsesDmaV3() == FALSE)
{
return m_DmaEnabler->GetDevice()->AcquireDmaPacketTransaction();
}
else
{
return STATUS_SUCCESS;
}
}
FORCEINLINE
VOID
ReleaseDevice(
VOID
)
{
if (m_DmaEnabler->UsesDmaV3() == FALSE)
{
m_DmaEnabler->GetDevice()->ReleaseDmaPacketTransaction();
}
}
_Must_inspect_result_
NTSTATUS
AllocateAdapterChannel(
__in BOOLEAN MapRegistersReserved
)
{
NTSTATUS status;
KIRQL irql;
KeRaiseIrql(DISPATCH_LEVEL, &irql);
if (GetDriverGlobals()->FxVerifierOn) {
if (MapRegistersReserved == FALSE) {
DoTraceLevelMessage(
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Allocating %d map registers for "
"WDFDMATRANSACTION %p",
m_MapRegistersNeeded,
GetHandle()
);
}
else {
DoTraceLevelMessage(
GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Using %d reserved map registers for "
"WDFDMATRANSACTION %p",
m_MapRegistersNeeded,
GetHandle()
);
}
}
if (m_DmaEnabler->UsesDmaV3()) {
PDMA_OPERATIONS dmaOperations =
m_AdapterInfo->AdapterObject->DmaOperations;
if (MapRegistersReserved == FALSE)
{
status = dmaOperations->AllocateAdapterChannelEx(
m_AdapterInfo->AdapterObject,
m_DmaEnabler->m_FDO,
GetTransferContext(),
m_MapRegistersNeeded,
m_Flags,
#pragma prefast(suppress: __WARNING_CLASS_MISMATCH_NONE, "This warning requires a wrapper class for the DRIVER_CONTROL type.")
_AdapterControl,
this,
NULL
);
}
else {
#pragma prefast(suppress:__WARNING_PASSING_FUNCTION_UNEXPECTED_NULL, "_AdapterControl does not actually use the IRP parameter.");
_AdapterControl(m_DmaEnabler->m_FDO,
NULL,
GetMapRegisterBase(),
this);
status = STATUS_SUCCESS;
}
}
else {
ASSERTMSG("Prereserved map registers are not compatible with DMA V2",
MapRegistersReserved == FALSE);
status = m_AdapterInfo->AdapterObject->DmaOperations->
AllocateAdapterChannel(m_AdapterInfo->AdapterObject,
m_DmaEnabler->m_FDO,
m_MapRegistersNeeded,
#pragma prefast(suppress: __WARNING_CLASS_MISMATCH_NONE, "This warning requires a wrapper class for the DRIVER_CONTROL type.")
_AdapterControl,
this);
}
KeLowerIrql(irql);
if (!NT_SUCCESS(status))
{
DoTraceLevelMessage(
GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGDMA,
"Allocating DMA resources (%d map registers) for WDFDMATRANSACTION %p "
"returned %!STATUS!",
m_MapRegistersNeeded,
GetHandle(),
status
);
}
return status;
}
FORCEINLINE
NTSTATUS
MapTransfer(
__out_bcount_opt(ScatterGatherListCb)
PSCATTER_GATHER_LIST ScatterGatherList,
__in ULONG ScatterGatherListCb,
__in_opt PDMA_COMPLETION_ROUTINE CompletionRoutine,
__in_opt PVOID CompletionContext,
__out ULONG *TransferLength
)
{
//
// Cache globals & object handle since call to MapTransferEx could
// result in a DmaComplete callback before returning.
//
PFX_DRIVER_GLOBALS pFxDriverGlobals = GetDriverGlobals();
WDFDMATRANSACTION handle = GetHandle();
#if DBG
ULONG_PTR mapRegistersRequired;
mapRegistersRequired = ADDRESS_AND_SIZE_TO_SPAN_PAGES(
GetStartVaFromOffset(m_CurrentFragmentMdl,
m_CurrentFragmentOffset),
m_CurrentFragmentLength
);
NT_ASSERTMSG("Mapping requires too many map registers",
mapRegistersRequired <= m_MapRegistersNeeded);
#endif
NTSTATUS status;
//
// Assume we're going to transfer the entire current fragment.
// MapTransfer may say otherwise.
//
*TransferLength = (ULONG) m_CurrentFragmentLength;
//
// Map the transfer.
//
if (pFxDriverGlobals->FxVerifierOn) {
DoTraceLevelMessage(pFxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Mapping transfer for WDFDMATRANSACTION %p. "
"MDL %p, Offset %I64x, Length %x, MapRegisterBase %p",
handle,
m_CurrentFragmentMdl,
m_CurrentFragmentOffset,
*TransferLength,
GetMapRegisterBase());
}
if (m_DmaEnabler->UsesDmaV3()) {
PDMA_OPERATIONS dmaOperations =
m_AdapterInfo->AdapterObject->DmaOperations;
status = dmaOperations->MapTransferEx(
m_AdapterInfo->AdapterObject,
m_CurrentFragmentMdl,
GetMapRegisterBase(),
m_CurrentFragmentOffset,
m_DeviceAddressOffset,
TransferLength,
(BOOLEAN) m_DmaDirection,
ScatterGatherList,
ScatterGatherListCb,
CompletionRoutine,
CompletionContext
);
NT_ASSERTMSG(
"With these parameters, MapTransferEx should never fail",
NT_SUCCESS(status) || status == STATUS_CANCELLED
);
}
else {
NT_ASSERTMSG("cannot use DMA completion routine with DMAv2",
CompletionRoutine == NULL);
NT_ASSERTMSG(
"scatter gather list length must be large enough for at least one element",
(ScatterGatherListCb >= (sizeof(SCATTER_GATHER_LIST) +
sizeof(SCATTER_GATHER_ELEMENT)))
);
//
// This matches the assertion above. There's no way to explain to
// prefast that this code path requires the caller to provide a buffer
// of sufficient size to store the SGL. The only case which doesn't
// provide any buffer is system-mode DMA and that uses DMA v3 and so
// won't go through this path.
//
__assume((ScatterGatherListCb >= (sizeof(SCATTER_GATHER_LIST) +
sizeof(SCATTER_GATHER_ELEMENT))));
ScatterGatherList->NumberOfElements = 1;
ScatterGatherList->Reserved = 0;
ScatterGatherList->Elements[0].Address =
m_AdapterInfo->AdapterObject->DmaOperations->
MapTransfer(m_AdapterInfo->AdapterObject,
m_CurrentFragmentMdl,
GetMapRegisterBase(),
GetStartVaFromOffset(m_CurrentFragmentMdl,
m_CurrentFragmentOffset),
TransferLength,
(BOOLEAN) m_DmaDirection);
ScatterGatherList->Elements[0].Length = *TransferLength;
status = STATUS_SUCCESS;
}
if (pFxDriverGlobals->FxVerifierOn) {
DoTraceLevelMessage(pFxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGDMA,
"MapTransfer mapped next %d bytes of "
"WDFDMATRANSACTION %p - status %!STATUS!",
*TransferLength,
handle,
status);
}
return status;
}
FORCEINLINE
NTSTATUS
FlushAdapterBuffers(
VOID
)
{
PDMA_OPERATIONS dmaOperations =
m_AdapterInfo->AdapterObject->DmaOperations;
NTSTATUS status;
#if DBG
ULONG_PTR mapRegistersRequired;
mapRegistersRequired = ADDRESS_AND_SIZE_TO_SPAN_PAGES(
GetStartVaFromOffset(m_CurrentFragmentMdl,
m_CurrentFragmentOffset),
m_CurrentFragmentLength
);
NT_ASSERTMSG("Mapping requires too many map registers",
mapRegistersRequired <= m_MapRegistersNeeded);
#endif
if (GetDriverGlobals()->FxVerifierOn) {
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Flushing DMA buffers for WDFDMATRANSACTION %p. "
"MDL %p, Offset %I64x, Length %I64x",
GetHandle(),
m_CurrentFragmentMdl,
m_CurrentFragmentOffset,
m_CurrentFragmentLength);
}
if (m_DmaEnabler->UsesDmaV3()) {
status = dmaOperations->FlushAdapterBuffersEx(
m_AdapterInfo->AdapterObject,
m_CurrentFragmentMdl,
GetMapRegisterBase(),
m_CurrentFragmentOffset,
(ULONG) m_CurrentFragmentLength,
(BOOLEAN) m_DmaDirection
);
}
else if (dmaOperations->FlushAdapterBuffers(
m_AdapterInfo->AdapterObject,
m_CurrentFragmentMdl,
GetMapRegisterBase(),
GetStartVaFromOffset(m_CurrentFragmentMdl,
m_CurrentFragmentOffset),
(ULONG) m_CurrentFragmentLength,
(BOOLEAN) m_DmaDirection) == FALSE) {
status = STATUS_UNSUCCESSFUL;
}
else {
status = STATUS_SUCCESS;
}
if (!NT_SUCCESS(status)) {
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_ERROR, TRACINGDMA,
"Flushing DMA buffers for WDFDMATRANSACTION %p ("
"MDL %p, Offset %I64x, Length %I64x)"
"completed with %!STATUS!",
GetHandle(),
m_CurrentFragmentMdl,
m_CurrentFragmentOffset,
m_CurrentFragmentLength,
status);
}
return status;
}
virtual
VOID
FreeMapRegistersAndAdapter(
VOID
)
{
KIRQL irql;
PVOID mapRegisterBase = GetMapRegisterBase();
//
// It's illegal to free a NULL map register base, even if the HAL gave it
// to us.
//
if (mapRegisterBase == NULL) {
if (GetDriverGlobals()->FxVerifierOn) {
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Skipping free of %d map registers for WDFDMATRANSACTION %p "
"because base was NULL",
m_MapRegistersNeeded,
GetHandle());
}
return;
}
//
// Free the map registers
//
KeRaiseIrql(DISPATCH_LEVEL, &irql);
if (GetDriverGlobals()->FxVerifierOn) {
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Freeing %d map registers for WDFDMATRANSACTION %p "
"(base %p)",
m_MapRegistersNeeded,
GetHandle(),
mapRegisterBase);
}
//
// If we pre-reserved map registers then Reserved contains
// the number to free. Otherwise Needed is the number allocated
// for the last transaction, which is the number to free.
//
m_AdapterInfo->AdapterObject->DmaOperations->
FreeMapRegisters(m_AdapterInfo->AdapterObject,
mapRegisterBase,
(m_MapRegistersReserved > 0 ?
m_MapRegistersReserved :
m_MapRegistersNeeded));
KeLowerIrql(irql);
return;
}
virtual
VOID
CallEvtDmaCompleted(
__in DMA_COMPLETION_STATUS /* Status */
)
{
//
// Packet mode DMA doesn't support cancellation or
// completion routines. So this should never run.
//
ASSERTMSG("EvtDmaCompleted is not a valid callback for "
"a packet-mode transaction",
FALSE);
return;
}
};
class FxDmaSystemTransaction: public FxDmaPacketTransaction {
friend FxDmaPacketTransaction;
public:
FxDmaSystemTransaction(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in USHORT ExtraSize,
__in FxDmaEnabler *DmaEnabler
);
_Must_inspect_result_
static
NTSTATUS
_Create(
__in PFX_DRIVER_GLOBALS FxDriverGlobals,
__in PWDF_OBJECT_ATTRIBUTES Attributes,
__in FxDmaEnabler* DmaEnabler,
__out WDFDMATRANSACTION* Transaction
);
VOID
SetConfigureChannelCallback(
__in_opt PFN_WDF_DMA_TRANSACTION_CONFIGURE_DMA_CHANNEL Callback,
__in_opt PVOID Context
)
{
m_ConfigureChannelFunction.Method = Callback;
m_ConfigureChannelContext = Context;
}
VOID
SetTransferCompleteCallback(
__in_opt PFN_WDF_DMA_TRANSACTION_DMA_TRANSFER_COMPLETE Callback,
__in_opt PVOID Context
)
{
m_TransferCompleteFunction.Method = Callback;
m_TransferCompleteContext = Context;
}
VOID
StopTransfer(
VOID
);
protected:
//
// Callback and context for configure channel callback
//
FxDmaTransactionConfigureChannel m_ConfigureChannelFunction;
PVOID m_ConfigureChannelContext;
//
// Callback and context for DMA completion callback
//
FxDmaTransactionTransferComplete m_TransferCompleteFunction;
PVOID m_TransferCompleteContext;
IO_ALLOCATION_ACTION
GetAdapterControlReturnValue(
VOID
)
{
return KeepObject;
}
VOID
FreeMapRegistersAndAdapter(
VOID
)
{
PFX_DRIVER_GLOBALS pFxDriverGlobals = GetDriverGlobals();
KIRQL irql;
KeRaiseIrql(DISPATCH_LEVEL, &irql);
if (pFxDriverGlobals->FxVerifierOn) {
DoTraceLevelMessage(pFxDriverGlobals, TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Freeing adapter channel for WDFDMATRANSACTION %p",
GetHandle());
}
m_AdapterInfo->AdapterObject->DmaOperations->
FreeAdapterChannel(m_AdapterInfo->AdapterObject);
KeLowerIrql(irql);
return;
}
BOOLEAN
CancelMappedTransfer(
VOID
)
{
NTSTATUS status;
ASSERT(m_DmaEnabler->UsesDmaV3());
//
// Cancel the transfer. if it's not yet mapped this will mark the
// TC so that mapping will fail. If it's running this will invoke the
// DMA completion routine.
//
status =
m_AdapterInfo->AdapterObject->DmaOperations->CancelMappedTransfer(
m_AdapterInfo->AdapterObject,
GetTransferContext()
);
DoTraceLevelMessage(GetDriverGlobals(), TRACE_LEVEL_VERBOSE, TRACINGDMA,
"Stopping WDFDMATRANSACTION %p returned status %!STATUS!",
GetHandle(),
status);
return NT_SUCCESS(status);
}
VOID
Reuse(
VOID
)
{
FxDmaPacketTransaction::Reuse(); // __super call
m_ConfigureChannelFunction.Method = NULL;
m_ConfigureChannelContext = NULL;
m_TransferCompleteFunction.Method = NULL;
m_TransferCompleteContext = NULL;
}
VOID
CallEvtDmaCompleted(
__in DMA_COMPLETION_STATUS Status
);
virtual
BOOLEAN
PreMapTransfer(
VOID
);
virtual
PDMA_COMPLETION_ROUTINE
GetTransferCompletionRoutine(
VOID
);
static DMA_COMPLETION_ROUTINE _SystemDmaCompletion;
};
#endif // _FXDMATRANSACTION_HPP_