Sample Code

Windows Driver Samples/ System DMA/ C++/ sys/ sdma.c/

/*++

Copyright (c) 2011  Microsoft Corporation All Rights Reserved

Module Name:

    sdma.c

Abstract:

    The purpose of this driver is to demonstrate V3 system DMA.

Environment:

    Kernel mode only.

--*/


//
// Include files.
//

#include <ntddk.h>          // various NT definitions
#include <string.h>

#include "sdma.h"

#define NT_DEVICE_NAME      L"\\Device\\SDMA"
#define DOS_DEVICE_NAME     L"\\DosDevices\\DmaTest"

#if DBG
#define SDMA_KDPRINT(_x_) \
                DbgPrint("SDMA.SYS: ");\
                DbgPrint _x_;

#else
#define SDMA_KDPRINT(_x_)
#endif

//
// These are the states a PDO or FDO transition upon
// receiving a specific PnP Irp. Refer to the PnP Device States
// diagram in DDK documentation for better understanding.
//

typedef enum _DEVICE_PNP_STATE {

    NotStarted = 0,         // Not started yet
    Started,                // Device has received the START_DEVICE IRP
    StopPending,            // Device has received the QUERY_STOP IRP
    Stopped,                // Device has received the STOP_DEVICE IRP
    RemovePending,          // Device has received the QUERY_REMOVE IRP
    SurpriseRemovePending,  // Device has received the SURPRISE_REMOVE IRP
    Deleted,                // Device has received the REMOVE_DEVICE IRP
    UnKnown                 // Unknown state

} DEVICE_PNP_STATE;

typedef struct _COMMON_DEVICE_DATA
{
    // A back pointer to the device object for which this is the extension

    PDEVICE_OBJECT  Self;

    // This flag helps distinguish between PDO and FDO

    BOOLEAN         IsFDO;

    // We track the state of the device with every PnP Irp
    // that affects the device through these two variables.
    
    DEVICE_PNP_STATE DevicePnPState;

    DEVICE_PNP_STATE PreviousPnPState;

    
    ULONG           DebugLevel;

    // Stores the current system power state
    
    SYSTEM_POWER_STATE  SystemPowerState;

    // Stores current device power state
    
    DEVICE_POWER_STATE  DevicePowerState;

    ULONG           ulVariationFlags;

} COMMON_DEVICE_DATA, *PCOMMON_DEVICE_DATA;

//
// The device extension of the bus itself.  From whence the PDO's are born.
//

typedef struct _FDO_DEVICE_DATA
{
    COMMON_DEVICE_DATA CommonData;

    PDEVICE_OBJECT  UnderlyingPDO;
    
    // The underlying bus PDO and the actual device object to which our
    // FDO is attached

    PDEVICE_OBJECT  NextLowerDriver;

    // List of PDOs created so far
    
    LIST_ENTRY      ListOfPDOs;
    
    // The PDOs currently enumerated.

    ULONG           NumPDOs;

    // A synchronization for access to the device extension.

    FAST_MUTEX      Mutex;

    //
    // The number of IRPs sent from the bus to the underlying device object
    //
    
    ULONG           OutstandingIO; // Biased to 1

    //
    // On remove device plug & play request we must wait until all outstanding
    // requests have been completed before we can actually delete the device
    // object. This event is when the Outstanding IO count goes to zero
    //

    KEVENT          RemoveEvent;

    //
    // This event is set when the Outstanding IO count goes to 1.
    //
    
    KEVENT          StopEvent;
    
    // The name returned from IoRegisterDeviceInterface,
    // which is used as a handle for IoSetDeviceInterfaceState.

    UNICODE_STRING      InterfaceName;

} FDO_DEVICE_DATA, *PFDO_DEVICE_DATA;

//
// Define minimum and maximum macros.
//

#define Minimum(_a, _b) (((_a) < (_b)) ? (_a) : (_b))
#define Maximum(_a, _b) (((_a) > (_b)) ? (_a) : (_b))

//
// Define the structure that we'll use to communicate with our
// AllocateAdapterChannel callback.
//

typedef struct _SDMA_CALLBACK_CONTEXT {
    IO_ALLOCATION_ACTION Action;
    ULONG  NumberOfMapRegisters;
    PVOID  MapRegisterBase;
    KEVENT CallBackEvent;
    KEVENT CompletionEvent;
} SDMA_CALLBACK_CONTEXT, *PSDMA_CALLBACK_CONTEXT;

//
// Device driver routine declarations.
//

DRIVER_INITIALIZE DriverEntry;

_Dispatch_type_(IRP_MJ_CREATE)
_Dispatch_type_(IRP_MJ_CLOSE)
DRIVER_DISPATCH SDmaCreateClose;

_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)
DRIVER_DISPATCH SDmaDeviceControl;

DRIVER_UNLOAD SDmaUnloadDriver;

VOID
PrintIrpInfo(
    PIRP Irp
    );
VOID
PrintChars(
    _In_reads_(CountChars) PCHAR BufferAddress,
    _In_ size_t CountChars
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, DriverEntry )
#pragma alloc_text( PAGE, SDmaCreateClose)
#pragma alloc_text( PAGE, SDmaDeviceControl)
#pragma alloc_text( PAGE, SDmaUnloadDriver)
#pragma alloc_text( PAGE, PrintIrpInfo)
#pragma alloc_text( PAGE, PrintChars)
#endif // ALLOC_PRAGMA


NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT   DriverObject,
    _In_ PUNICODE_STRING      RegistryPath
    )
/*++

Routine Description:
    This routine is called by the Operating System to initialize the driver.

    It creates the device object, fills in the dispatch entry points and
    completes the initialization.

Arguments:
    DriverObject - a pointer to the object that represents this device
    driver.

    RegistryPath - a pointer to our Services key in the registry.

Return Value:
    STATUS_SUCCESS if initialized; an error otherwise.

--*/

{
    NTSTATUS        ntStatus;
    UNICODE_STRING  ntUnicodeString;    // NT Device Name "\Device\SDMA"
    UNICODE_STRING  ntWin32NameString;    // Win32 Name "\DosDevices\DmaTest"
    PDEVICE_OBJECT  deviceObject = NULL;    // ptr to device object

    UNREFERENCED_PARAMETER(RegistryPath);

    RtlInitUnicodeString( &ntUnicodeString, NT_DEVICE_NAME );

    ntStatus = IoCreateDevice(
        DriverObject,                   // Our Driver Object
        0,                              // We don't use a device extension
        &ntUnicodeString,               // Device name "\Device\SDMA"
        FILE_DEVICE_UNKNOWN,            // Device type
        FILE_DEVICE_SECURE_OPEN,     // Device characteristics
        FALSE,                          // Not an exclusive device
        &deviceObject );                // Returned ptr to Device Object

    if ( !NT_SUCCESS( ntStatus ) )
    {
        SDMA_KDPRINT(("Couldn't create the device object\n"));
        return ntStatus;
    }

    //
    // Initialize the driver object with this driver's entry points.
    //

    DriverObject->MajorFunction[IRP_MJ_CREATE] = SDmaCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = SDmaCreateClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = SDmaDeviceControl;
    DriverObject->DriverUnload = SDmaUnloadDriver;

    //
    // Initialize a Unicode String containing the Win32 name
    // for our device.
    //

    RtlInitUnicodeString( &ntWin32NameString, DOS_DEVICE_NAME );

    //
    // Create a symbolic link between our device name  and the Win32 name
    //

    ntStatus = IoCreateSymbolicLink(
                        &ntWin32NameString, &ntUnicodeString );

    if ( !NT_SUCCESS( ntStatus ) )
    {
        //
        // Delete everything that this routine has allocated.
        //
        SDMA_KDPRINT(("Couldn't create symbolic link\n"));
        IoDeleteDevice( deviceObject );
    }


    return ntStatus;
}


NTSTATUS
SDmaCreateClose(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )
/*++

Routine Description:

    This routine is called by the I/O system when the driver is opened or
    closed.

    No action is performed other than completing the request successfully.

Arguments:

    DeviceObject - a pointer to the object that represents the device
    that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    NT status code

--*/

{
    UNREFERENCED_PARAMETER(DeviceObject);

    PAGED_CODE();

    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;

    IoCompleteRequest( Irp, IO_NO_INCREMENT );

    return STATUS_SUCCESS;
}

VOID
SDmaUnloadDriver(
    _In_ PDRIVER_OBJECT DriverObject
    )
/*++

Routine Description:

    This routine is called by the I/O system to unload the driver.

    Any resources previously allocated must be freed.

Arguments:

    DriverObject - a pointer to the object that represents our driver.

Return Value:

    None
--*/

{
    PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject;
    UNICODE_STRING uniWin32NameString;

    PAGED_CODE();

    //
    // Create counted string version of our Win32 device name.
    //

    RtlInitUnicodeString( &uniWin32NameString, DOS_DEVICE_NAME );


    //
    // Delete the link from our device name to a name in the Win32 namespace.
    //

    IoDeleteSymbolicLink( &uniWin32NameString );

    if ( deviceObject != NULL )
    {
        IoDeleteDevice( deviceObject );
    }



}

//
// These helper routines allow MDL creation and destruction
// as well as providing simple DMA callback routines for
// channel allocation and DMA completion.
//

ULONG
MDL_SPAN(
    _In_ PMDL  Mdl,
    _In_ PVOID CurrentVa,
         ULONG Length
    )

/*++

Routine Description:

    This function determines the number of pages spanned by the given MDL.
    We have to account for the case where the MDL is actually the head of
    a chain.

Arguments:

    Mdl - Supplies the MDL being checked.

    CurrentVa - Provides our starting address within the first MDL.

    Length - Supplies the total number of bytes from the given MDL chain that
        are associated with this transfer.

Return Value:

    This function returns the number of pages spanned by the specified
    transfer.

--*/

{
    PVOID VirtualAddress;
    ULONG Span;
    ULONG RemainingLength;
    ULONG ChunkLength;
    ULONG MdlOffset;

    if (Length == 0)
    {
        return 0;
    }

    if (Mdl->Next == NULL)
    {
        return ADDRESS_AND_SIZE_TO_SPAN_PAGES(CurrentVa, Length);
    }

    Span = 0;
    RemainingLength = Length;
    VirtualAddress = CurrentVa;

    MdlOffset = (ULONG) (((ULONG_PTR) CurrentVa)
                         - ((ULONG_PTR) MmGetMdlVirtualAddress(Mdl)));

    while (Mdl != NULL)
    {
        ChunkLength = Minimum(MmGetMdlByteCount(Mdl) - MdlOffset,
                              RemainingLength);

        Span += ADDRESS_AND_SIZE_TO_SPAN_PAGES(VirtualAddress, ChunkLength);

        Mdl = Mdl->Next;
        MdlOffset = 0;
        RemainingLength -= ChunkLength;

        if (Mdl != NULL)
        {
            VirtualAddress = MmGetMdlVirtualAddress(Mdl);
        }

    }

    return Span;
}

VOID
SDmaFreeMdl(
    _In_opt_ PMDL MdlHead
    )

/*++

Routine Description:

    This function frees an MDL that was allocated earlier with a call to
    DmaUnitAllocateMdl.

Arguments:

    MdlHead - Supplies the MDL to free.  This could be the head of a chain
        of MDLs.

Return Value:

    None.

--*/

{
    PMDL Mdl;
    PMDL NextMdl;

    Mdl = MdlHead;

    while (Mdl != NULL) 
    {
        if ((Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) != 0)
        {
            MmUnmapLockedPages(Mdl->MappedSystemVa, Mdl);
        }

        MmFreePagesFromMdl(Mdl);

        NextMdl = Mdl->Next;
        ExFreePool(Mdl);

        Mdl = NextMdl;
    }

    return;
}

VOID SDmaFillMdl(
    _In_ PMDL Mdl,
    _In_ PVOID CurrentVa,
    _In_ ULONG Length,
    _In_ UCHAR Fill
    )

/*++

Routine Description:

    This function fills an MDL with a data byte.

Arguments:

    Mdl - Supplies the MDL that describes the system pages associated with
        this transfer.

    CurrentVa - Provides the starting address of the transfer within
        the given MDL.

    Length - Supplies the length of the transfer in bytes.

    Fill   - The byte to write into the MDL.

Return Value:

    None

--*/

{
    ULONG MdlOffset;
    ULONG PageOffset;
    ULONG ChunkLength;
    PCHAR VirtualAddress;
    ULONG RemainingLength;

    RemainingLength = Length;
    while (Mdl != NULL)
    {

        MdlOffset = (ULONG) (((ULONG_PTR) CurrentVa)
                    - ((ULONG_PTR) MmGetMdlVirtualAddress(Mdl)));

        ASSERT((Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) != 0);

        PageOffset = BYTE_OFFSET(CurrentVa);
        VirtualAddress = PAGE_ALIGN(((PCHAR) Mdl->MappedSystemVa) + MdlOffset);

        while ((MdlOffset != MmGetMdlByteCount(Mdl))
               && (RemainingLength != 0))
        {

            ChunkLength = Minimum(MmGetMdlByteCount(Mdl) - MdlOffset,
                                  RemainingLength);

            ChunkLength = Minimum(ChunkLength, PAGE_SIZE - PageOffset);

            ASSERT((PageOffset + ChunkLength) <= PAGE_SIZE);

            RtlFillMemory(((PCHAR) VirtualAddress) + PageOffset, ChunkLength,
                         Fill);

            PageOffset = 0;
            RemainingLength -= ChunkLength;
            MdlOffset += ChunkLength;
            VirtualAddress = VirtualAddress + ChunkLength;
        }

        Mdl = Mdl->Next;
    }
}

VOID SDmaPrintMdl(
    _In_ PMDL Mdl,
    _In_ PVOID CurrentVa,
    _In_ ULONG Length,
    _In_ PCHAR TitleStr
    )

/*++

Routine Description:

    This function prints MDL information to the debugger.

Arguments:

    Mdl - Supplies the MDL to be displayed.

    CurrentVa - Provides the starting address of the transfer within
        the given MDL.

    Length - Supplies the length of the transfer in bytes.

    TitleStr - A text tile to print describing this MDL.

Return Value:

    None

--*/

{
    ULONG MdlOffset;
    PCHAR VirtualAddress;
    ULONG MdlCount = 0;
    ULONG TotalLength = 0;
    PPFN_NUMBER PageFrame;
    ULONG NumPages = 0;
    ULONG PageCount = 0;
    PMDL MdlHead;
    PHYSICAL_ADDRESS PageAddress;
    ULONG RemainingLength;
    ULONG ChunkLength;

    MdlOffset = (ULONG) (((ULONG_PTR) CurrentVa)
                         - ((ULONG_PTR) MmGetMdlVirtualAddress(Mdl)));

    MdlHead = Mdl;
    NumPages = MDL_SPAN(MdlHead, CurrentVa, Length);
    while (Mdl != NULL)
    {

        TotalLength += Minimum(MmGetMdlByteCount(Mdl) - MdlOffset,
                               Length);

        Mdl = Mdl->Next;
        MdlCount++;
    }

    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "########### %s ########\n",
               TitleStr == NULL ? "MDL" : TitleStr );
    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "Mdl Head = 0x%p\n", MdlHead);
    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "Mdl Count = %d\n", MdlCount);
    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "Total Byte Count = %d\n", TotalLength);
    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "Total Physical Pages = %d\n", NumPages);

    Mdl = MdlHead;
    MdlCount = 0;
    RemainingLength = Length;
    while (Mdl != NULL)
    {

        MdlOffset = (ULONG) (((ULONG_PTR) CurrentVa)
                            - ((ULONG_PTR) MmGetMdlVirtualAddress(Mdl)));

        ASSERT((Mdl->MdlFlags & MDL_MAPPED_TO_SYSTEM_VA) != 0);

        VirtualAddress = PAGE_ALIGN(((PCHAR) Mdl->MappedSystemVa) + MdlOffset);
        ChunkLength = Minimum(MmGetMdlByteCount(Mdl) - MdlOffset,
                              RemainingLength);

        NumPages = ADDRESS_AND_SIZE_TO_SPAN_PAGES(VirtualAddress, ChunkLength);

        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "########################################\n");
        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "Mdl #%d: 0x%p\n", MdlCount, Mdl);
        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "    Mdl Offset = %d\n", MdlOffset);
        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "    Mdl Length = %d\n", Mdl->ByteCount);
        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "    Mdl Base VA = 0x%p\n", VirtualAddress);
        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "------------ PHYSICAL PAGES ------------\n");
        PageFrame = MmGetMdlPfnArray( Mdl );

        for (PageCount = 0; PageCount < NumPages; PageCount++)
        {
            PageAddress.QuadPart = (ULONGLONG)(*(PageFrame + PageCount)) << PAGE_SHIFT;
            DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "    Page #%d: 0x%I64x\n", PageCount, PageAddress.QuadPart);
        }

        Mdl = Mdl->Next;
        RemainingLength -= ChunkLength;
        MdlCount++;
    }
    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "########################################\n");
}

PMDL
SDmaAllocateMdl(
    _Out_ PVOID *CurrentVa,
    _Out_ PULONG Length,
    _In_  BOOLEAN ForceContiguous
    )

/*++

Routine Description:

    This function allocates a single MDL.

Arguments:

    CurrentVa - Supplies the location where we should store the starting
        virtual address of the transfer within the allocated MDL.

    Length - Supplies the location where we should store the length of
        the requested transfer.

Return Value:

    This function returns a pointer to a single MDL.

--*/

{
    PMDL MdlHead;
    PMDL Mdl;
    ULONG Span;
    ULONG RequestBytes;
    PHYSICAL_ADDRESS LowAddress;
    PHYSICAL_ADDRESS HighAddress;
    PHYSICAL_ADDRESS SkipAddress;
    PVOID MappingAddress;
    ULONG TransferLength;
    ULONG Offset;

	*Length = 0;
	*CurrentVa = NULL;
    LowAddress.QuadPart = 0;
    HighAddress.QuadPart = 0xffffffffffffffffI64;
    SkipAddress.QuadPart = 0;

    //
    // Add one MDL.
    //

    MdlHead = NULL;
    TransferLength = 0;

    {
        Span = 2;
        RequestBytes = Span << PAGE_SHIFT;

        if (ForceContiguous)
        {
            Mdl = MmAllocatePagesForMdlEx(LowAddress,
                                          HighAddress,
                                          SkipAddress,
                                          RequestBytes,
                                          MmCached,
                                          MM_ALLOCATE_REQUIRE_CONTIGUOUS_CHUNKS
                                          );
        }
        else
        {
            Mdl = MmAllocatePagesForMdl(LowAddress,
                                        HighAddress,
                                        SkipAddress,
                                        RequestBytes
                                        );
        }

        if (Mdl == NULL)
        {
            DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "MmAllocatePagesForMdl failed. \n" );
            goto Cleanup;
        }

        if (MmGetMdlByteCount(Mdl) < RequestBytes)
        {
            DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "MmAllocatePagesForMdl allocated less than requested bytes. \n" );
            goto Cleanup;
        }

        //
        // Set the byte count and byte offset appropriately for this MDL.
        // Note that this is safe since we allocated enough memory to cover
        // the entire page span up above.
        //

        Mdl->ByteCount = RequestBytes;
        Mdl->ByteOffset = 0;

        MappingAddress = MmMapLockedPagesSpecifyCache(
                                Mdl,
                                KernelMode,
                                MmCached,
                                NULL,
                                FALSE,
                                HighPagePriority);

        if (MappingAddress == NULL)
        {
            DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "MmMapLockedPagesSpecifyCache failed. \n" );
            goto Cleanup;
        }

        if (MdlHead == NULL)
        {
            MdlHead = Mdl;
        } 

        TransferLength += RequestBytes;
    }

    Offset = 0;

    *CurrentVa =
        (PVOID) (((ULONG_PTR) MmGetMdlVirtualAddress(MdlHead)) + Offset);

    *Length = TransferLength - Offset;

    return MdlHead;

    //
    // We'll branch to this point when we need to free the active MDL and
    // the MDL that we've built up to this point.
    //

Cleanup:

    if (Mdl != NULL)
    {
        MmFreePagesFromMdl(Mdl);
        ExFreePool(Mdl);
    }

	if (MdlHead != NULL)
	{
		SDmaFreeMdl(MdlHead);
	}

    return NULL;
}

_Function_class_(DRIVER_CONTROL)
IO_ALLOCATION_ACTION
SDmaAdapterControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID MapRegisterBase,
    IN PVOID Context
    )

/*++

Routine Description:

    This is the AdapterControl routine that we'll use to complete calls
    to AllocateAdapterChannelEx.

Arguments:

    DeviceObject - Supplies the FDO associated with this AllocateAdapterChannelEx
        call.

    Irp - Unused.

    MapRegisterBase - Supplies the handle to our allocated map registers.

    Context - Supplies the callback context that was passed to
        AllocateAdapterChannelEx.  In our case this will be a
        context structure.

Return Value:

    This function returns the Action parameter from the given callback
    context structure.

Environment:

    DISPATCH_LEVEL.

--*/

{
    IO_ALLOCATION_ACTION Action;
    PSDMA_CALLBACK_CONTEXT CallbackContext;

    UNREFERENCED_PARAMETER(Irp);
	UNREFERENCED_PARAMETER(DeviceObject);

    CallbackContext = (PSDMA_CALLBACK_CONTEXT) Context;

    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "AllocateAdapterChannelEx called back."
               " MapRegisterBase = 0x%IX\n", (ULONG_PTR)MapRegisterBase
        );

    //
    // Make a note of the allocated map register base and return the handle
    // associated with this map register allocation to the caller.
    //
    CallbackContext->MapRegisterBase    = MapRegisterBase;

    Action = CallbackContext->Action;

    KeSetEvent(&(CallbackContext->CallBackEvent), IO_NO_INCREMENT, FALSE);

    return Action;
}

_Function_class_(DMA_COMPLETION_ROUTINE)
VOID
SDmaCompletion(
    IN PDMA_ADAPTER DmaAdapter,
    IN PDEVICE_OBJECT DeviceObject,
    IN PVOID Context,
    IN DMA_COMPLETION_STATUS Status
    )

/*++

Routine Description:

    This is the DMA completion routine that we'll use to complete calls
    to MapTransferEx.

Arguments:

    DeviceObject - Supplies the FDO associated with this MapTransferEx
        call.

    Context - Supplies the callback context that was passed to
        MapTransferEx.  In our case this will be a
        context structure.

Return Value:

    None.

Environment:

    DISPATCH_LEVEL.

--*/

{
    PSDMA_CALLBACK_CONTEXT CallbackContext;

    UNREFERENCED_PARAMETER(DmaAdapter);
    UNREFERENCED_PARAMETER(DeviceObject);

    CallbackContext = (PSDMA_CALLBACK_CONTEXT) Context;

    DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "MapTransferEx called back. Status = %d\n",
        Status
        );

    KeSetEvent(&(CallbackContext->CompletionEvent), IO_NO_INCREMENT, FALSE);
}

NTSTATUS
SDmaWrite(
    _In_    PFDO_DEVICE_DATA pFdoData
    )

/*++

Routine Description:

    This function initiates simple system DMA against a
    non-existent DMA controller. The code is provided
	only for example purposes and is not intended as product
	code.

Arguments:

    pFdoData - Supplies the FDO extension

Return Value:

    This function returns a NTSTATUS value.

--*/

{
    KIRQL Irql;
    ULONG DmaRequestLine = 0x1000;
    NTSTATUS Status = STATUS_SUCCESS;
    PDMA_ADAPTER pWriteAdapter = NULL;
    ULONG AllocatableMapRegisters = 0, RequestedMapRegisters = 2;
    DEVICE_DESCRIPTION Description;
    SDMA_CALLBACK_CONTEXT WriteContext;
    CHAR WriteDmaTransferContext[ DMA_TRANSFER_CONTEXT_SIZE_V1 ];
    PMDL Mdl0 = NULL;
    PVOID CurrentVa = NULL;
    ULONG Length = 0;

    //
    // Allocate a system DMA adapter to write data to a device
    // (for instance the Tx register of a UART) using system DMA.
    //

    ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL);

    RtlZeroMemory(&Description, sizeof(Description));
    Description.Version           = DEVICE_DESCRIPTION_VERSION3;
    Description.DmaAddressWidth   = 32;
    Description.DmaRequestLine    = DmaRequestLine;
    Description.DmaChannel        = DmaRequestLine;
    Description.InterfaceType     = ACPIBus;
    Description.Master            = FALSE;
    Description.ScatterGather     = TRUE;
    Description.MaximumLength     = RequestedMapRegisters << PAGE_SHIFT;

    pWriteAdapter = IoGetDmaAdapter(pFdoData->UnderlyingPDO,
                                     &Description,
                                     &AllocatableMapRegisters);

    //
    // This is the expected return path on all systems, since no
    // system DMA controller supports request line 0x1000.
    //
    if ( pWriteAdapter == NULL )
    {
        DbgPrintEx(DPFLTR_IHVBUS_ID, 0, "IoGetDmaAdapter failed for request line %d. \n", Description.DmaRequestLine );
        Status = STATUS_NO_MATCH;
        goto Exit;
    }

    RtlZeroMemory( &WriteContext, sizeof( WriteContext ) );
    WriteContext.Action = KeepObject;
    WriteContext.NumberOfMapRegisters = AllocatableMapRegisters;
    KeInitializeEvent(&(WriteContext.CallBackEvent), NotificationEvent, FALSE);
    KeInitializeEvent(&(WriteContext.CompletionEvent), NotificationEvent, FALSE);

    pWriteAdapter->DmaOperations->InitializeDmaTransferContext(
        pWriteAdapter,
        (PVOID)&(WriteDmaTransferContext));

    DbgPrintEx(DPFLTR_IHVBUS_ID, 0,
        "%p WRITE V3 System DmaAdapter created: 32 bit, Scatter-Gather capable, %d MapRegs (%d requested)\n", 
        pWriteAdapter, AllocatableMapRegisters, RequestedMapRegisters
        );

    //
    // Call AllocateAdapterChannelEx for the write adapter.
    //
    KeRaiseIrql(DISPATCH_LEVEL, &Irql);

    Status = pWriteAdapter->DmaOperations->AllocateAdapterChannelEx(
        pWriteAdapter,
        pFdoData->CommonData.Self,
        &(WriteDmaTransferContext),
        AllocatableMapRegisters,
        0,
        SDmaAdapterControl,
        (PVOID) &WriteContext,
        NULL);
    KeLowerIrql(Irql);

    if (Status != STATUS_SUCCESS)
    {
        goto Exit;
    }

    //
    // Wait for the channel to be allocated to our driver. A real driver would
    // not block this IRP to wait for AllocateAdapterChannelEx to call
    // back the AdapterControl routine. Instead all the steps below (including
    // the MapTransferEx) would be staged from with the AdapterControl
    // routine.
    //

    KeWaitForSingleObject(&(WriteContext.CallBackEvent), Executive, KernelMode, FALSE, NULL);

    //
    // We have acquired a channel for Write adapter successfully.
    //

    //
    // Build a fake MDL to represent user data.
    // Mdl0 is the address for the Write channel, and is the
    // data (likely from usermode in real world scenarios) that
    // will be written to the device address (this address may
    // be the Tx register of a UART device for instance).
    //

    Mdl0 = SDmaAllocateMdl(&CurrentVa, &Length, FALSE);

    //
    // Fill the MDL with 0xDA bytes.
    //

    SDmaPrintMdl( Mdl0, CurrentVa, Length, "Mdl0" );
    SDmaFillMdl( Mdl0, CurrentVa, Length, 0xDA );

    //
    // Signal the DMA engine to write the entire length of the
    // MDL using V3 system DMA.
    // We just use a 0 offset since our CurrentVa starts at the 
    // beginning of the MDL.
    //
    
    pWriteAdapter->DmaOperations->MapTransferEx(
        pWriteAdapter,
        Mdl0,
        WriteContext.MapRegisterBase,
        0,
        0x70006200, // Physical address of hardware
                    // device
                    // (e.g. UART Tx register)
        &Length,
        TRUE,
        NULL,
        0,
        SDmaCompletion,
        &WriteContext);

    //
    // Wait for DMA to complete on the channel. A real driver
    // would not block this IRP to wait but would instead handle the
    // FlushAdapterBuffersEx/FreeAdapterChannel steps during the DMA
    // completion routine.
    //

    KeWaitForSingleObject(&(WriteContext.CompletionEvent), Executive, KernelMode, FALSE, NULL);
    
    //
    // Copy the data in the map buffers (if any) back to the MDL.
    //
    
    pWriteAdapter->DmaOperations->FlushAdapterBuffersEx(
        pWriteAdapter,
        Mdl0,
        WriteContext.MapRegisterBase,
        0,
        Length,
        TRUE);

    //
    // Now we can free the MDL associated with this transfer.
    //

    SDmaFreeMdl(Mdl0);

    //
    // Return the channel back to the HAL. If this action is
    // not taken, then nothing else in the OS will be able to
    // use this channel again until it is freed. This includes
    // our own driver handling another IOCTL.
    //

    KeRaiseIrql(DISPATCH_LEVEL, &Irql);
    pWriteAdapter->DmaOperations->FreeAdapterChannel( pWriteAdapter );
    KeLowerIrql(Irql);

Exit:

    //
    // Free the DMA adapter. Typically a driver will not Get and Put
    // a DMA adapter for each DMA transaction, but instead will perform
    // these actions once upon device driver construction and destruction.
    //

    ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
    
    if (pWriteAdapter != NULL)
    {
        pWriteAdapter->DmaOperations->PutDmaAdapter(pWriteAdapter);
    }

    //
    // Send our return status.
    //

    return Status;
}

NTSTATUS
SDmaDeviceControl(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp
    )

/*++

Routine Description:

    This routine is called by the I/O system to perform a device I/O
    control function.

Arguments:

    DeviceObject - a pointer to the object that represents the device
        that I/O is to be done on.

    Irp - a pointer to the I/O Request Packet for this request.

Return Value:

    NT status code

--*/

{
    PIO_STACK_LOCATION  irpSp;// Pointer to current stack location
    NTSTATUS            ntStatus = STATUS_SUCCESS;// Assume success
    ULONG               inBufLength; // Input buffer length
    ULONG               outBufLength; // Output buffer length
    PCHAR               inBuf; // pointer to Input buffer
    PCHAR               data = "This String is from Device Driver !!!";
    size_t              datalen = strlen(data)+1;//Length of data including null
    PFDO_DEVICE_DATA    fdoData;

    PAGED_CODE();

    fdoData = (PFDO_DEVICE_DATA) DeviceObject->DeviceExtension;
    irpSp = IoGetCurrentIrpStackLocation( Irp );
    inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
    outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

    if (!inBufLength || !outBufLength)
    {
        ntStatus = STATUS_INVALID_PARAMETER;
        goto End;
    }

    //
    // Determine which I/O control code was specified.
    //

    switch ( irpSp->Parameters.DeviceIoControl.IoControlCode )
    {
    case IOCTL_SDMA_WRITE:

        //
        // In this method the I/O manager allocates a buffer large enough to
        // to accommodate larger of the user input buffer and output buffer,
        // assigns the address to Irp->AssociatedIrp.SystemBuffer, and
        // copies the content of the user input buffer into this SystemBuffer
        //

        SDMA_KDPRINT(("Called IOCTL_SDMA_WRITE\n"));
        PrintIrpInfo(Irp);

        //
        // Input buffer and output buffer is same in this case, read the
        // content of the buffer before writing to it
        //

        inBuf = Irp->AssociatedIrp.SystemBuffer;
#if 0
        outBuf = Irp->AssociatedIrp.SystemBuffer;
#endif

        //
        // Read the data from the buffer
        //

        SDMA_KDPRINT(("\tData from User :"));
        //
        // We are using the following function to print characters instead
        // of DebugPrint with %s format because the string we get may or
        // may not be null terminated.
        //
        PrintChars(inBuf, inBufLength);

        //
        // Call the example V3 system DMA routine. The routine
        // is expected to fail because no system DMA controller
        // will match the one requested within the routine.
        //

        SDmaWrite( fdoData );

#if 0
        //
        // Write to the buffer over-writes the input buffer content
        //

        RtlCopyBytes(outBuf, data, outBufLength);

        SDMA_KDPRINT(("\tData to User : "));
        PrintChars(outBuf, datalen  );
#endif

        //
        // Assign the length of the data copied to IoStatus.Information
        // of the Irp and complete the Irp.
        //

        Irp->IoStatus.Information = (outBufLength<datalen?outBufLength:datalen);

        //
        // When the Irp is completed the content of the SystemBuffer
        // is copied to the User output buffer and the SystemBuffer is
        // is freed.
        //

       break;

    default:

        //
        // The specified I/O control code is unrecognized by this driver.
        //

        ntStatus = STATUS_INVALID_DEVICE_REQUEST;
        SDMA_KDPRINT(("ERROR: unrecognized IOCTL %x\n",
            irpSp->Parameters.DeviceIoControl.IoControlCode));
        break;
    }

End:
    //
    // Finish the I/O operation by simply completing the packet and returning
    // the same status as in the packet itself.
    //

    Irp->IoStatus.Status = ntStatus;

    IoCompleteRequest( Irp, IO_NO_INCREMENT );

    return ntStatus;
}

VOID
PrintIrpInfo(
    PIRP Irp)
{
    PIO_STACK_LOCATION  irpSp;
    irpSp = IoGetCurrentIrpStackLocation( Irp );

    PAGED_CODE();

    SDMA_KDPRINT(("\tIrp->AssociatedIrp.SystemBuffer = 0x%p\n",
        Irp->AssociatedIrp.SystemBuffer));
    SDMA_KDPRINT(("\tIrp->UserBuffer = 0x%p\n", Irp->UserBuffer));
    SDMA_KDPRINT(("\tirpSp->Parameters.DeviceIoControl.Type3InputBuffer = 0x%p\n",
        irpSp->Parameters.DeviceIoControl.Type3InputBuffer));
    SDMA_KDPRINT(("\tirpSp->Parameters.DeviceIoControl.InputBufferLength = %d\n",
        irpSp->Parameters.DeviceIoControl.InputBufferLength));
    SDMA_KDPRINT(("\tirpSp->Parameters.DeviceIoControl.OutputBufferLength = %d\n",
        irpSp->Parameters.DeviceIoControl.OutputBufferLength ));

	UNREFERENCED_PARAMETER(irpSp);
    return;
}

VOID
PrintChars(
    _In_reads_(CountChars) PCHAR BufferAddress,
    _In_ size_t CountChars
    )
{
    PAGED_CODE();

    if (CountChars) {

        while (CountChars--) {

            if (*BufferAddress > 31
                 && *BufferAddress != 127) {

                KdPrint (( "%c", *BufferAddress) );

            } else {

                KdPrint(( ".") );

            }
            BufferAddress++;
        }
        KdPrint (("\n"));
    }
    return;
}


Our Services

  • What our customers say about us?

© 2011-2024 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS