Sample Code

Windows Driver Samples/ SkeletonI2C Sample Driver/ C++/ device.cpp/

/*++

Copyright (c) Microsoft Corporation.  All rights reserved.

Module Name: 

    device.cpp

Abstract:

    This module contains WDF device initialization 
    and SPB callback functions for the controller driver.

Environment:

    kernel-mode only

Revision History:

--*/

#include "internal.h"
#include "device.h"
#include "controller.h"

#include "device.tmh"


/////////////////////////////////////////////////
//
// WDF and SPB DDI callbacks.
//
/////////////////////////////////////////////////

NTSTATUS
OnPrepareHardware(
    _In_  WDFDEVICE    FxDevice,
    _In_  WDFCMRESLIST FxResourcesRaw,
    _In_  WDFCMRESLIST FxResourcesTranslated
    )
/*++
 
  Routine Description:

    This routine maps the hardware resources to the SPB
    controller register structure.

  Arguments:

    FxDevice - a handle to the framework device object
    FxResourcesRaw - list of translated hardware resources that 
        the PnP manager has assigned to the device
    FxResourcesTranslated - list of raw hardware resources that 
        the PnP manager has assigned to the device

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);

    PPBC_DEVICE pDevice = GetDeviceContext(FxDevice);
    NT_ASSERT(pDevice != NULL);
    
    NTSTATUS status = STATUS_SUCCESS; 

    UNREFERENCED_PARAMETER(FxResourcesRaw);
    
    //
    // Get the register base for the I2C controller.
    //

    {
        ULONG resourceCount = WdfCmResourceListGetCount(FxResourcesTranslated);

        for(ULONG i = 0; i < resourceCount; i++)
        {
            PCM_PARTIAL_RESOURCE_DESCRIPTOR res;
           
            res = WdfCmResourceListGetDescriptor(FxResourcesTranslated, i);

            if (res->Type == CmResourceTypeMemory)
            {
                pDevice->pRegisters = 
                    (PSKELETONI2C_REGISTERS) MmMapIoSpace(
                        res->u.Memory.Start,
                        res->u.Memory.Length,
                        MmNonCached);

                pDevice->RegistersCb = res->u.Memory.Length;

                if (pDevice->pRegisters == NULL)
                {
                    status = STATUS_INSUFFICIENT_RESOURCES;

                    Trace(
                        TRACE_LEVEL_ERROR,
                        TRACE_FLAG_WDFLOADING,
                        "Error mapping controller registers (PA:%I64x, length:%d) "
                        "for WDFDEVICE %p - %!STATUS!", 
                        res->u.Memory.Start.QuadPart,
                        res->u.Memory.Length,
                        pDevice->FxDevice,
                        status);
                    
                    NT_ASSERT(pDevice->pRegisters != NULL);

                    goto exit;
                }

                //
                // Save the physical address to help identify
                // the underlying controller while debugging.
                //

                pDevice->pRegistersPhysicalAddress = res->u.Memory.Start;

                Trace(
                    TRACE_LEVEL_INFORMATION, 
                    TRACE_FLAG_WDFLOADING, 
                    "I2C controller @ paddr %I64x vaddr @ %p for WDFDEVICE %p", 
                    pDevice->pRegistersPhysicalAddress.QuadPart,
                    pDevice->pRegisters,
                    pDevice->FxDevice);
            }
        }
    }

exit:
    
    FuncExit(TRACE_FLAG_WDFLOADING);

    return status;
}

NTSTATUS
OnReleaseHardware(
    _In_  WDFDEVICE    FxDevice,
    _In_  WDFCMRESLIST FxResourcesTranslated
    )
/*++
 
  Routine Description:

    This routine unmaps the SPB controller register structure.

  Arguments:

    FxDevice - a handle to the framework device object
    FxResourcesRaw - list of translated hardware resources that 
        the PnP manager has assigned to the device
    FxResourcesTranslated - list of raw hardware resources that 
        the PnP manager has assigned to the device

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);

    PPBC_DEVICE pDevice = GetDeviceContext(FxDevice);
    NT_ASSERT(pDevice != NULL);
    
    NTSTATUS status = STATUS_SUCCESS;
    
    UNREFERENCED_PARAMETER(FxResourcesTranslated);
    
    if (pDevice->pRegisters != NULL)
    {
        MmUnmapIoSpace(pDevice->pRegisters, pDevice->RegistersCb);
        
        pDevice->pRegisters = NULL;
        pDevice->RegistersCb = 0;
    }

    FuncExit(TRACE_FLAG_WDFLOADING);

    return status;
}

NTSTATUS
OnD0Entry(
    _In_  WDFDEVICE              FxDevice,
    _In_  WDF_POWER_DEVICE_STATE FxPreviousState
    )
/*++
 
  Routine Description:

    This routine allocates objects needed by the driver 
    and initializes the controller hardware.

  Arguments:

    FxDevice - a handle to the framework device object
    FxPreviousState - previous power state

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);
    
    PPBC_DEVICE pDevice = GetDeviceContext(FxDevice);
    NT_ASSERT(pDevice != NULL);
    
    UNREFERENCED_PARAMETER(FxPreviousState);

    //
    // Initialize controller.
    //

    pDevice->pCurrentTarget = NULL;

    ControllerInitialize(pDevice);
    
    FuncExit(TRACE_FLAG_WDFLOADING);

    return STATUS_SUCCESS;
}

NTSTATUS
OnD0Exit(
    _In_  WDFDEVICE              FxDevice,
    _In_  WDF_POWER_DEVICE_STATE FxPreviousState
    )
/*++
 
  Routine Description:

    This routine destroys objects needed by the driver 
    and uninitializes the controller hardware.

  Arguments:

    FxDevice - a handle to the framework device object
    FxPreviousState - previous power state

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);

    PPBC_DEVICE pDevice = GetDeviceContext(FxDevice);
    NT_ASSERT(pDevice != NULL);
    
    NTSTATUS status = STATUS_SUCCESS;
    
    UNREFERENCED_PARAMETER(FxPreviousState);

    //
    // Uninitialize controller.
    //

    ControllerUninitialize(pDevice);

    pDevice->pCurrentTarget = NULL;

    FuncExit(TRACE_FLAG_WDFLOADING);

    return status;
}

NTSTATUS
OnSelfManagedIoInit(
    _In_  WDFDEVICE  FxDevice
    )
/*++
 
  Routine Description:

    Initializes and starts the device's self-managed I/O operations.

  Arguments:
  
    FxDevice - a handle to the framework device object

  Return Value:

    None

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);

    PPBC_DEVICE pDevice = GetDeviceContext(FxDevice);
    NTSTATUS status;

    // 
    // Register for monitor power setting callback. This will be
    // used to dynamically set the idle timeout delay according
    // to the monitor power state.
    // 

    NT_ASSERT(pDevice->pMonitorPowerSettingHandle == NULL);
    
    status = PoRegisterPowerSettingCallback(
        WdfDeviceWdmGetDeviceObject(pDevice->FxDevice), 
        &GUID_MONITOR_POWER_ON,
        OnMonitorPowerSettingCallback, 
        (PVOID)pDevice->FxDevice, 
        &pDevice->pMonitorPowerSettingHandle);

    if (!NT_SUCCESS(status))
    {
        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_WDFLOADING,
            "Failed to register monitor power setting callback - %!STATUS!",
            status);
                
        goto exit;
    }

exit:

    FuncExit(TRACE_FLAG_WDFLOADING);

    return status;
}

VOID
OnSelfManagedIoCleanup(
    _In_  WDFDEVICE  FxDevice
    )
/*++
 
  Routine Description:

    Cleanup for the device's self-managed I/O operations.

  Arguments:
  
    FxDevice - a handle to the framework device object

  Return Value:

    None

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);

    PPBC_DEVICE pDevice = GetDeviceContext(FxDevice);

    //
    // Unregister for monitor power setting callback.
    //

    if (pDevice->pMonitorPowerSettingHandle != NULL)
    {
        PoUnregisterPowerSettingCallback(pDevice->pMonitorPowerSettingHandle);
        pDevice->pMonitorPowerSettingHandle = NULL;
    }

    FuncExit(TRACE_FLAG_WDFLOADING);
}

__drv_functionClass(POWER_SETTING_CALLBACK)
_IRQL_requires_same_
NTSTATUS
OnMonitorPowerSettingCallback(
    _In_                          LPCGUID SettingGuid,
    _In_reads_bytes_(ValueLength) PVOID   Value,
    _In_                          ULONG   ValueLength,
    _Inout_opt_                   PVOID   Context
   )
/*++
 
  Routine Description:

    This routine updates the idle timeout delay according 
    to the current monitor power setting.

  Arguments:

    SettingGuid - the setting GUID
    Value - pointer to the new value of the power setting that changed
    ValueLength - value of type ULONG that specifies the size, in bytes, 
                  of the new power setting value 
    Context - the WDFDEVICE pointer context

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_WDFLOADING);

    UNREFERENCED_PARAMETER(ValueLength);

    WDFDEVICE Device;
    WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS idleSettings;
    BOOLEAN isMonitorOff;
    NTSTATUS status = STATUS_SUCCESS;

    if (Context == NULL)
    {
        status = STATUS_INVALID_PARAMETER;

        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_WDFLOADING,
            "%!FUNC! parameter Context is NULL - %!STATUS!",
            status);

        goto exit;
    }

    Device = (WDFDEVICE)Context;

    //
    // We only expect GUID_MONITOR_POWER_ON notifications
    // in this callback, but let's check just to be sure.
    //

    if (IsEqualGUID(*SettingGuid, GUID_MONITOR_POWER_ON))
    {
        NT_ASSERT(Value != NULL);
        NT_ASSERT(ValueLength == sizeof(ULONG));

        //
        // Determine power setting.
        //

        isMonitorOff = ((*(PULONG)Value) == MONITOR_POWER_OFF);

        //
        // Update the idle timeout delay.
        //

        WDF_DEVICE_POWER_POLICY_IDLE_SETTINGS_INIT(
            &idleSettings, 
            IdleCannotWakeFromS0);

        idleSettings.IdleTimeoutType = SystemManagedIdleTimeoutWithHint;

        if (isMonitorOff)
        { 
            idleSettings.IdleTimeout = IDLE_TIMEOUT_MONITOR_OFF;
        }
        else
        {
            idleSettings.IdleTimeout = IDLE_TIMEOUT_MONITOR_ON;
 
        }

        status = WdfDeviceAssignS0IdleSettings(
            Device, 
            &idleSettings);

        if (!NT_SUCCESS(status))
        {
            Trace(
                TRACE_LEVEL_ERROR,
                TRACE_FLAG_WDFLOADING,
                "Failed to assign S0 idle settings - %!STATUS!",
                status);
                
            goto exit;
        }
    }

exit:
    
    FuncExit(TRACE_FLAG_WDFLOADING);
 
    return status;
}

NTSTATUS
OnTargetConnect(
    _In_  WDFDEVICE  SpbController,
    _In_  SPBTARGET  SpbTarget
    )
/*++
 
  Routine Description:

    This routine is invoked whenever a peripheral driver opens
    a target.  It retrieves target-specific settings from the
    Resource Hub and saves them in the target's context.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    PPBC_DEVICE pDevice  = GetDeviceContext(SpbController);
    PPBC_TARGET pTarget  = GetTargetContext(SpbTarget);
    
    NT_ASSERT(pDevice != NULL);
    NT_ASSERT(pTarget != NULL);
    
    NTSTATUS status = STATUS_SUCCESS;

    //
    // Get target connection parameters.
    //

    SPB_CONNECTION_PARAMETERS params;
    SPB_CONNECTION_PARAMETERS_INIT(&params);

    SpbTargetGetConnectionParameters(SpbTarget, &params);

    //
    // Retrieve target settings.
    //

    status = PbcTargetGetSettings(pDevice,
                                  params.ConnectionParameters,
                                  &pTarget->Settings
                                  );
    
    //
    // Initialize target context.
    //

    if (NT_SUCCESS(status))
    {
        pTarget->SpbTarget = SpbTarget;
        pTarget->pCurrentRequest = NULL;

        Trace(
            TRACE_LEVEL_INFORMATION,
            TRACE_FLAG_SPBDDI,
            "Connected to SPBTARGET %p at address 0x%lx from WDFDEVICE %p",
            pTarget->SpbTarget,
            pTarget->Settings.Address,
            pDevice->FxDevice);
    }
    
    FuncExit(TRACE_FLAG_SPBDDI);

    return status;
}

VOID
OnControllerLock(
    _In_  WDFDEVICE   SpbController,
    _In_  SPBTARGET   SpbTarget,
    _In_  SPBREQUEST  SpbRequest
    )
/*++
 
  Routine Description:

    This routine is invoked whenever the controller is to
    be locked for a single target. The request is only completed
    if there is an error configuring the transfer.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object
    SpbRequest - a handle to the SPBREQUEST object

  Return Value:

    None.  The request is completed synchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    PPBC_DEVICE  pDevice  = GetDeviceContext(SpbController);
    PPBC_TARGET  pTarget  = GetTargetContext(SpbTarget);
    
    NT_ASSERT(pDevice  != NULL);
    NT_ASSERT(pTarget  != NULL);

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    //
    // Assign current target.
    //

    NT_ASSERT(pDevice->pCurrentTarget == NULL);

    pDevice->pCurrentTarget = pTarget;

    WdfSpinLockRelease(pDevice->Lock);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_SPBDDI,
        "Controller locked for SPBTARGET %p at address 0x%lx (WDFDEVICE %p)",
        pTarget->SpbTarget,
        pTarget->Settings.Address,
        pDevice->FxDevice);

    //
    // Complete lock request.
    //

    SpbRequestComplete(SpbRequest, STATUS_SUCCESS);
    
    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnControllerUnlock(
    _In_  WDFDEVICE   SpbController,
    _In_  SPBTARGET   SpbTarget,
    _In_  SPBREQUEST  SpbRequest
    )
/*++
 
  Routine Description:

    This routine is invoked whenever the controller is to
    be unlocked for a single target. The request is only completed
    if there is an error configuring the transfer.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object
    SpbRequest - a handle to the SPBREQUEST object

  Return Value:

    None.  The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    PPBC_DEVICE  pDevice  = GetDeviceContext(SpbController);
    PPBC_TARGET  pTarget  = GetTargetContext(SpbTarget);
    
    NT_ASSERT(pDevice  != NULL);
    NT_ASSERT(pTarget  != NULL);

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    // TODO: Check if there is an active sequence
    //       and if so perform any action necessary
    //       to stop the transfer in process.

    //
    // Remove current target.
    //

    NT_ASSERT(pDevice->pCurrentTarget == pTarget);

    pDevice->pCurrentTarget = NULL;
    
    WdfSpinLockRelease(pDevice->Lock);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_SPBDDI,
        "Controller unlocked for SPBTARGET %p at address 0x%lx (WDFDEVICE %p)",
        pTarget->SpbTarget,
        pTarget->Settings.Address,
        pDevice->FxDevice);

    //
    // Complete lock request.
    //

    SpbRequestComplete(SpbRequest, STATUS_SUCCESS);
    
    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnRead(
    _In_  WDFDEVICE   SpbController,
    _In_  SPBTARGET   SpbTarget,
    _In_  SPBREQUEST  SpbRequest,
    _In_  size_t      Length
    )
/*++
 
  Routine Description:

    This routine sets up a read from the target device using
    the supplied buffers.  The request is only completed
    if there is an error configuring the transfer.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object
    SpbRequest - a handle to the SPBREQUEST object
    Length - the number of bytes to read from the target

  Return Value:

    None.  The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_SPBDDI,
        "Received read request %p of length %Iu for SPBTARGET %p "
        "(WDFDEVICE %p)",
        SpbRequest,
        Length,
        SpbTarget,
        SpbController);

    PbcRequestConfigureForNonSequence(
        SpbController,
        SpbTarget,
        SpbRequest,
        Length);

    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnWrite(
    _In_  WDFDEVICE   SpbController,
    _In_  SPBTARGET   SpbTarget,
    _In_  SPBREQUEST  SpbRequest,
    _In_  size_t      Length
    )
/*++
 
  Routine Description:

    This routine sets up a write to the target device using
    the supplied buffers.  The request is only completed
    if there is an error configuring the transfer.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object
    SpbRequest - a handle to the SPBREQUEST object
    Length - the number of bytes to write to the target

  Return Value:

    None.  The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_SPBDDI,
        "Received write request %p of length %Iu for SPBTARGET %p "
        "(WDFDEVICE %p)",
        SpbRequest,
        Length,
        SpbTarget,
        SpbController);

    PbcRequestConfigureForNonSequence(
        SpbController,
        SpbTarget,
        SpbRequest,
        Length);

    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnSequence(
    _In_  WDFDEVICE   SpbController,
    _In_  SPBTARGET   SpbTarget,
    _In_  SPBREQUEST  SpbRequest,
    _In_  ULONG       TransferCount
    )
/*++
 
  Routine Description:

    This routine sets up a sequence of reads and writes.  It 
    validates parameters as necessary.  The request is only 
    completed if there is an error configuring the transfer.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object
    SpbRequest - a handle to the SPBREQUEST object
    TransferCount - number of individual transfers in the sequence

  Return Value:

    None.  The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    PPBC_DEVICE  pDevice  = GetDeviceContext(SpbController);
    PPBC_TARGET  pTarget  = GetTargetContext(SpbTarget);
    PPBC_REQUEST pRequest = GetRequestContext(SpbRequest);
    BOOLEAN completeRequest = FALSE;
    
    NT_ASSERT(pDevice  != NULL);
    NT_ASSERT(pTarget  != NULL);
    NT_ASSERT(pRequest != NULL);
    
    NTSTATUS status = STATUS_SUCCESS;
    
    //
    // Get request parameters.
    //

    SPB_REQUEST_PARAMETERS params;
    SPB_REQUEST_PARAMETERS_INIT(&params);
    SpbRequestGetParameters(SpbRequest, &params);
    
    NT_ASSERT(params.Position == SpbRequestSequencePositionSingle);
    NT_ASSERT(params.Type == SpbRequestTypeSequence);

    //
    // Initialize request context.
    //
    
    pRequest->SpbRequest = SpbRequest;
    pRequest->Type = params.Type;
    pRequest->TotalInformation = 0;
    pRequest->TransferCount = TransferCount;
    pRequest->TransferIndex = 0;
    pRequest->bIoComplete = FALSE;

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_SPBDDI,
        "Received sequence request %p with transfer count %d for SPBTARGET %p "
        "(WDFDEVICE %p)",
        pRequest->SpbRequest,
        pRequest->TransferCount,
        SpbTarget,
        SpbController);

    //
    // Validate the request before beginning the transfer.
    //
    
    status = PbcRequestValidate(pRequest);

    if (!NT_SUCCESS(status))
    {
        goto exit;
    }
    
    //
    // Configure the request.
    //

    status = PbcRequestConfigureForIndex(pRequest, 0);

    if (!NT_SUCCESS(status))
    {
        Trace(
            TRACE_LEVEL_ERROR, 
            TRACE_FLAG_SPBDDI, 
            "Error configuring request context for SPBREQUEST %p "
            "(SPBTARGET %p) - %!STATUS!",
            pRequest->SpbRequest,
            SpbTarget,
            status);

        goto exit;
    }

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    //
    // Mark request cancellable (if cancellation supported).
    //

    status = WdfRequestMarkCancelableEx(
        pRequest->SpbRequest, OnCancel);

    if (!NT_SUCCESS(status))
    {
        //
        // WdfRequestMarkCancelableEx should only fail if the request
        // has already been cancelled. If it does fail the request
        // must be completed with the corresponding status.
        //

        NT_ASSERTMSG("WdfRequestMarkCancelableEx should only fail if the request has already been cancelled",
            status == STATUS_CANCELLED);

        Trace(
            TRACE_LEVEL_INFORMATION,
            TRACE_FLAG_TRANSFER,
            "Failed to mark SPBREQUEST %p cancellable - %!STATUS!",
            pRequest->SpbRequest,
            status);
        
        WdfSpinLockRelease(pDevice->Lock);
        goto exit;
    }
    
    //
    // Update device and target contexts.
    //

    NT_ASSERT(pDevice->pCurrentTarget == NULL);
    NT_ASSERT(pTarget->pCurrentRequest == NULL);
    
    pDevice->pCurrentTarget = pTarget;
    pTarget->pCurrentRequest = pRequest;
    
    //
    // Configure controller and kick-off read.
    // Request will be completed asynchronously.
    //
    
    PbcRequestDoTransfer(pDevice, pRequest);
    
    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. This must be done outside of 
    //       the locked code.
    if (pRequest->bIoComplete)
    {
        completeRequest = TRUE;
    }
    
    WdfSpinLockRelease(pDevice->Lock);
    
    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. This must be done outside of 
    //       the locked code.
    if (completeRequest)
    {
        PbcRequestComplete(pRequest);
    }

exit:

    if (!NT_SUCCESS(status))
    {
        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_SPBDDI,
            "Error configuring sequence, completing "
            "SPBREQUEST %p synchronously - %!STATUS!", 
            pRequest->SpbRequest,
            status);

        SpbRequestComplete(SpbRequest, status);
    }
    
    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnOtherInCallerContext(
    _In_  WDFDEVICE   SpbController,
    _In_  WDFREQUEST  FxRequest
    )
/*++
 
  Routine Description:

    This routine preprocesses custom IO requests before the framework
    places them in an IO queue. For requests using the SPB transfer list
    format, it calls SpbRequestCaptureIoOtherTransferList to capture the
    client's buffers.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbRequest - a handle to the SPBREQUEST object

  Return Value:

    None.  The request is either completed or enqueued asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);

    NTSTATUS status;

    //
    // Check for custom IOCTLs that this driver handles. If
    // unrecognized mark as STATUS_NOT_SUPPORTED and complete.
    //

    WDF_REQUEST_PARAMETERS fxParams;
    WDF_REQUEST_PARAMETERS_INIT(&fxParams);

    WdfRequestGetParameters(FxRequest, &fxParams);

    if ((fxParams.Type != WdfRequestTypeDeviceControl) &&
        (fxParams.Type != WdfRequestTypeDeviceControlInternal))
    {
        status = STATUS_NOT_SUPPORTED;
        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_SPBDDI,
            "FxRequest %p is of unsupported request type - %!STATUS!",
            FxRequest,
            status
            );
        goto exit;
    }

    //
    // TODO: verify the driver supports this DeviceIoContol code,
    //       otherwise mark as STATUS_NOT_SUPPORTED and complete.
    //

    //
    // For custom IOCTLs that use the SPB transfer list format
    // (i.e. sequence formatting), call SpbRequestCaptureIoOtherTransferList
    // so that the driver can leverage other SPB DDIs for this request.
    //

    status = SpbRequestCaptureIoOtherTransferList((SPBREQUEST)FxRequest);

    if (!NT_SUCCESS(status))
    {
        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_SPBDDI,
            "Failed to capture transfer list for custom SpbRequest %p"
            " - %!STATUS!",
            FxRequest,
            status
            );
        goto exit;
    }

    //
    // Preprocessing has succeeded, enqueue the request.
    //

    status = WdfDeviceEnqueueRequest(SpbController, FxRequest);

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

exit:

    if (!NT_SUCCESS(status))
    {
        WdfRequestComplete(FxRequest, status);
    }
    
    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnOther(
    _In_  WDFDEVICE   SpbController,
    _In_  SPBTARGET   SpbTarget,
    _In_  SPBREQUEST  SpbRequest,
    _In_  size_t      OutputBufferLength,
    _In_  size_t      InputBufferLength,
    _In_  ULONG       IoControlCode
    )
/*++
 
  Routine Description:

    This routine processes custom IO requests that are not natively
    supported by the SPB framework extension. For requests using the 
    SPB transfer list format, SpbRequestCaptureIoOtherTransferList 
    must have been called in the driver's OnOtherInCallerContext routine.

  Arguments:

    SpbController - a handle to the framework device object
        representing an SPB controller
    SpbTarget - a handle to the SPBTARGET object
    SpbRequest - a handle to the SPBREQUEST object
    OutputBufferLength - the request's output buffer length
    InputBufferLength - the requests input buffer length
    IoControlCode - the device IO control code

  Return Value:

    None.  The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_SPBDDI);
    
    NTSTATUS status = STATUS_SUCCESS;

    UNREFERENCED_PARAMETER(SpbController);
    UNREFERENCED_PARAMETER(SpbTarget);
    UNREFERENCED_PARAMETER(SpbRequest);
    UNREFERENCED_PARAMETER(OutputBufferLength);
    UNREFERENCED_PARAMETER(InputBufferLength);
    UNREFERENCED_PARAMETER(IoControlCode);

    //
    // TODO: the driver should take the following steps
    //
    //    1. Verify this specific DeviceIoContol code is supported,
    //       otherwise mark as STATUS_NOT_SUPPORTED and complete.
    //
    //    2. If this IOCTL uses SPB_TRANSFER_LIST and the driver has
    //       called SpbRequestCaptureIoOtherTransferList previously,
    //       validate the request format. The driver can make use of
    //       SpbRequestGetTransferParameters to retrieve each transfer
    //       descriptor.
    //
    //       If this IOCTL uses some proprietary buffer formating 
    //       instead of SPB_TRANSFER_LIST, validate appropriately.
    //
    //    3. Setup the device, target, and request contexts as necessary,
    //       and program the hardware for the transfer.
    //


    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. Note this must be done outside
    //       of any locked code.
    SpbRequestComplete(SpbRequest, status);
    
    FuncExit(TRACE_FLAG_SPBDDI);
}

VOID
OnCancel(
  _In_  WDFREQUEST  FxRequest
)

/*++
 
  Routine Description:

    This routine cancels an outstanding request. It
    must synchronize with other driver callbacks.

  Arguments:

    wdfRequest - a handle to the WDFREQUEST object

  Return Value:

    None.  The request is completed with status.

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    SPBREQUEST spbRequest = (SPBREQUEST) FxRequest;
    PPBC_DEVICE pDevice;
    PPBC_TARGET pTarget;
    PPBC_REQUEST pRequest;
    BOOLEAN bTransferCompleted = FALSE;

    //
    // Get the contexts.
    //

    pDevice = GetDeviceContext(SpbRequestGetController(spbRequest));
    pTarget = GetTargetContext(SpbRequestGetTarget(spbRequest));
    pRequest = GetRequestContext(spbRequest);
    
    NT_ASSERT(pDevice  != NULL);
    NT_ASSERT(pTarget  != NULL);
    NT_ASSERT(pRequest != NULL);

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    //
    // Make sure the current target and request 
    // are valid.
    //
    
    if (pTarget != pDevice->pCurrentTarget)
    {
        Trace(
            TRACE_LEVEL_WARNING,
            TRACE_FLAG_TRANSFER,
            "Cancel callback without a valid current target for WDFDEVICE %p, "
            "this should only occur if SPBREQUEST %p was already completed",
            pDevice->FxDevice,
            spbRequest
            );

        goto exit;
    }

    if (pRequest != pTarget->pCurrentRequest)
    {
        Trace(
            TRACE_LEVEL_WARNING,
            TRACE_FLAG_TRANSFER,
            "Cancel callback without a valid current request for SPBTARGET %p, "
            "this should only occur if SPBREQUEST %p was already completed",
            pTarget->SpbTarget,
            spbRequest);

        goto exit;
    }

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_TRANSFER,
        "Cancel callback with outstanding SPBREQUEST %p, "
        "stop IO and complete it",
        spbRequest);

    //
    // Stop delay timer.
    //

    if(WdfTimerStop(pDevice->DelayTimer, FALSE))
    {
        Trace(
            TRACE_LEVEL_INFORMATION,
            TRACE_FLAG_TRANSFER,
            "Delay timer previously schedule, now stopped");
    }
    
    //
    // Disable interrupts and clear saved stat for DPC. 
    // Must synchronize with ISR.
    //

    NT_ASSERT(pDevice->InterruptObject != NULL);

    // TODO: Uncomment when using interrupts.
    //WdfInterruptAcquireLock(pDevice->InterruptObject);

    ControllerDisableInterrupts(pDevice);
    pDevice->InterruptStatus = 0;
    
    //
    // TODO: Implement any necessary logic to abort the
    //       current IO operation. For I2C this requires
    //       driving a stop bit on the bus.
    //
    
    // TODO: Uncomment when using interrupts.
    //WdfInterruptReleaseLock(pDevice->InterruptObject);

    //
    // Mark request as cancelled and complete.
    //

    pRequest->Status = STATUS_CANCELLED;

    ControllerCompleteTransfer(pDevice, pRequest, TRUE);
    NT_ASSERT(pRequest->bIoComplete == TRUE);
    bTransferCompleted = TRUE;

exit:

    //
    // Release the device lock.
    //

    WdfSpinLockRelease(pDevice->Lock);

    //
    // Complete the request. There shouldn't be more IO.
    // This must be done outside of the locked code.
    //

    if (bTransferCompleted)
    {
        PbcRequestComplete(pRequest);
    }

    FuncExit(TRACE_FLAG_SPBDDI);
}


/////////////////////////////////////////////////
//
// Interrupt handling functions.
//
/////////////////////////////////////////////////

BOOLEAN
OnInterruptIsr(
    _In_  WDFINTERRUPT Interrupt,
    _In_  ULONG        MessageID
    )
/*++
 
  Routine Description:

    This routine responds to interrupts generated by the
    controller. If one is recognized, it queues a DPC for 
    processing. The interrupt is acknowledged and subsequent
    interrupts are temporarily disabled.

  Arguments:

    Interrupt - a handle to a framework interrupt object
    MessageID - message number identifying the device's
        hardware interrupt message (if using MSI)

  Return Value:

    TRUE if interrupt recognized.

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    BOOLEAN interruptRecognized = FALSE;
    ULONG stat;
    PPBC_DEVICE pDevice = GetDeviceContext(
        WdfInterruptGetDevice(Interrupt));

    UNREFERENCED_PARAMETER(MessageID);

    NT_ASSERT(pDevice  != NULL);

    //
    // Queue a DPC if the device's interrupt
    // is enabled and active.
    //

    stat = ControllerGetInterruptStatus(
        pDevice,
        PbcDeviceGetInterruptMask(pDevice));

    if (stat > 0)
    {
        Trace(
            TRACE_LEVEL_VERBOSE,
            TRACE_FLAG_TRANSFER,
            "Interrupt with status 0x%lx for WDFDEVICE %p",
            stat,
            pDevice->FxDevice);

        //
        // Save the interrupt status and disable all other
        // interrupts for now.  They will be re-enabled
        // in OnInterruptDpc.  Queue the DPC.
        //

        interruptRecognized = TRUE;
        
        pDevice->InterruptStatus |= (stat);
        ControllerDisableInterrupts(pDevice);
        
        if(!WdfInterruptQueueDpcForIsr(Interrupt))
        {
            Trace(
                TRACE_LEVEL_INFORMATION,
                TRACE_FLAG_TRANSFER,
                "Interrupt with status 0x%lx occurred with "
                "DPC already queued for WDFDEVICE %p",
                stat,
                pDevice->FxDevice);
        }
    }

    FuncExit(TRACE_FLAG_TRANSFER);
    
    return interruptRecognized;
}

VOID
OnInterruptDpc(
    _In_  WDFINTERRUPT Interrupt,
    _In_  WDFOBJECT    WdfDevice
    )
/*++
 
  Routine Description:

    This routine processes interrupts from the controller.
    When finished it reenables interrupts as appropriate.

  Arguments:

    Interrupt - a handle to a framework interrupt object
    WdfDevice - a handle to the framework device object

  Return Value:

    None.

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    PPBC_DEVICE pDevice;
    PPBC_TARGET pTarget;
    PPBC_REQUEST pRequest = NULL;
    ULONG stat;
    BOOLEAN bInterruptsProcessed = FALSE;
    BOOLEAN completeRequest = FALSE;

    UNREFERENCED_PARAMETER(Interrupt);

    pDevice = GetDeviceContext(WdfDevice);
    NT_ASSERT(pDevice != NULL);

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    //
    // Make sure the target and request are
    // still valid.
    //

    pTarget = pDevice->pCurrentTarget;
    
    if (pTarget == NULL)
    {
        Trace(
            TRACE_LEVEL_WARNING,
            TRACE_FLAG_TRANSFER,
            "DPC scheduled without a valid current target for WDFDEVICE %p, "
            "this should only occur if the request was already cancelled",
            pDevice->FxDevice);

        goto exit;
    }

    pRequest = pTarget->pCurrentRequest;

    if (pRequest == NULL)
    {
        Trace(
            TRACE_LEVEL_WARNING,
            TRACE_FLAG_TRANSFER,
            "DPC scheduled without a valid current request for SPBTARGET %p, "
            "this should only occur if the request was already cancelled",
            pTarget->SpbTarget);

        goto exit;
    }

    NT_ASSERT(pRequest->SpbRequest != NULL);

    //
    // Synchronize shared data buffers with ISR.
    // Copy interrupt status and clear shared buffer.
    // If there is a current target and request,
    // a DPC should never occur with interrupt status 0.
    //

    // TODO: Uncomment when using interrupts.
    //WdfInterruptAcquireLock(Interrupt);

    stat = pDevice->InterruptStatus;
    pDevice->InterruptStatus = 0;

    // TODO: Uncomment when using interrupts.
    //WdfInterruptReleaseLock(Interrupt);

    if (stat == 0)
    {
        goto exit;
    }

    Trace(
        TRACE_LEVEL_VERBOSE,
        TRACE_FLAG_TRANSFER,
        "DPC for interrupt with status 0x%lx for WDFDEVICE %p",
        stat,
        pDevice->FxDevice);

    //
    // Acknowledge and process interrupts.
    //

    ControllerAcknowledgeInterrupts(pDevice, stat);

    ControllerProcessInterrupts(pDevice, pRequest, stat);
    bInterruptsProcessed = TRUE;
    if (pRequest->bIoComplete)
    {
        completeRequest = TRUE;
    }

    //
    // Re-enable interrupts if necessary. Synchronize with ISR.
    //

    // TODO: Uncomment when using interrupts.
    //WdfInterruptAcquireLock(Interrupt);

    ULONG mask = PbcDeviceGetInterruptMask(pDevice);

    if (mask > 0)
    {
        Trace(
            TRACE_LEVEL_VERBOSE,
            TRACE_FLAG_TRANSFER,
            "Re-enable interrupts with mask 0x%lx for WDFDEVICE %p",
            mask,
            pDevice->FxDevice);

        ControllerEnableInterrupts(pDevice, mask);
    }

    // TODO: Uncomment when using interrupts.
    //WdfInterruptReleaseLock(Interrupt);

exit:

    //
    // Release the device lock.
    //

    WdfSpinLockRelease(pDevice->Lock);

    //
    // Complete the request if necessary.
    // This must be done outside of the locked code.
    //

    if (bInterruptsProcessed)
    {
        if (completeRequest)
        {
            PbcRequestComplete(pRequest);
        }
    }

    FuncExit(TRACE_FLAG_TRANSFER);
}


/////////////////////////////////////////////////
//
// PBC functions.
//
/////////////////////////////////////////////////

NTSTATUS
PbcTargetGetSettings(
    _In_  PPBC_DEVICE                pDevice,
    _In_  PVOID                      ConnectionParameters,
    _Out_ PPBC_TARGET_SETTINGS       pSettings
    )
/*++
 
  Routine Description:

    This routine populates the target's settings.

  Arguments:

    pDevice - a pointer to the PBC device context
    ConnectionParameters - a pointer to a blob containing the 
        connection parameters
    Settings - a pointer the the target's settings

  Return Value:

    Status

--*/
{
    FuncEntry(TRACE_FLAG_PBCLOADING);

    UNREFERENCED_PARAMETER(pDevice);

    NT_ASSERT(ConnectionParameters != nullptr);
    NT_ASSERT(pSettings != nullptr);

    PRH_QUERY_CONNECTION_PROPERTIES_OUTPUT_BUFFER connection;
    PPNP_SERIAL_BUS_DESCRIPTOR descriptor;
    PPNP_I2C_SERIAL_BUS_DESCRIPTOR i2cDescriptor;

    connection = (PRH_QUERY_CONNECTION_PROPERTIES_OUTPUT_BUFFER)
        ConnectionParameters;

    if (connection->PropertiesLength < sizeof(PNP_SERIAL_BUS_DESCRIPTOR))
    {
        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_PBCLOADING,
            "Invalid connection properties (length = %lu, "
            "expected = %Iu)",
            connection->PropertiesLength,
            sizeof(PNP_SERIAL_BUS_DESCRIPTOR));

        return STATUS_INVALID_PARAMETER;
    }

    descriptor = (PPNP_SERIAL_BUS_DESCRIPTOR)
        connection->ConnectionProperties;

    if (descriptor->SerialBusType != I2C_SERIAL_BUS_TYPE)
    {
        Trace(
            TRACE_LEVEL_ERROR,
            TRACE_FLAG_PBCLOADING,
            "Bus type %c not supported, only I2C",
            descriptor->SerialBusType);

        return STATUS_INVALID_PARAMETER;
    }

    i2cDescriptor = (PPNP_I2C_SERIAL_BUS_DESCRIPTOR)
        connection->ConnectionProperties;

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_PBCLOADING,
        "I2C Connection Descriptor %p "
        "ConnectionSpeed:%lu "
        "Address:0x%hx",
        i2cDescriptor,
        i2cDescriptor->ConnectionSpeed,
        i2cDescriptor->SlaveAddress);

    // Target address
    pSettings->Address = (ULONG)i2cDescriptor->SlaveAddress;

    // Address mode
    USHORT i2cFlags = i2cDescriptor->SerialBusDescriptor.TypeSpecificFlags;
    pSettings->AddressMode = 
        ((i2cFlags & I2C_SERIAL_BUS_SPECIFIC_FLAG_10BIT_ADDRESS) == 0) ? 
            AddressMode7Bit : AddressMode10Bit;

    // Clock speed
    pSettings->ConnectionSpeed = i2cDescriptor->ConnectionSpeed;

    FuncExit(TRACE_FLAG_PBCLOADING);

    return STATUS_SUCCESS;
}

NTSTATUS
PbcRequestValidate(
    _In_  PPBC_REQUEST               pRequest)
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    SPB_TRANSFER_DESCRIPTOR descriptor;
    NTSTATUS status = STATUS_SUCCESS;

    //
    // Validate each transfer descriptor.
    //

    for (ULONG i = 0; i < pRequest->TransferCount; i++)
    {
        //
        // Get transfer parameters for index.
        //

        SPB_TRANSFER_DESCRIPTOR_INIT(&descriptor);

        SpbRequestGetTransferParameters(
            pRequest->SpbRequest, 
            i, 
            &descriptor, 
            nullptr);

        //
        // Validate the transfer length.
        //
    
        if (descriptor.TransferLength > SI2C_MAX_TRANSFER_LENGTH)
        {
            status = STATUS_INVALID_PARAMETER;

            Trace(
                TRACE_LEVEL_ERROR, 
                TRACE_FLAG_TRANSFER, 
                "Transfer length %Iu is too large for controller driver, "
                "max supported is %d (SPBREQUEST %p, index %lu) - %!STATUS!",
                descriptor.TransferLength,
                SI2C_MAX_TRANSFER_LENGTH,
                pRequest->SpbRequest,
                i,
                status);

            goto exit;
        }
    }

exit:

    FuncExit(TRACE_FLAG_TRANSFER);

    return status;
}

VOID
PbcRequestConfigureForNonSequence(
    _In_  WDFDEVICE                  SpbController,
    _In_  SPBTARGET                  SpbTarget,
    _In_  SPBREQUEST                 SpbRequest,
    _In_  size_t                     Length
    )
/*++
 
  Routine Description:

    This is a generic helper routine used to configure
    the request context and controller hardware for a non-
    sequence SPB request. It validates parameters and retrieves
    the transfer buffer as necessary.

  Arguments:

    pDevice - a pointer to the PBC device context
    pTarget - a pointer to the PBC target context
    pRequest - a pointer to the PBC request context
    Length - the number of bytes to read from the target
    Direction - direction of the transfer

  Return Value:

    STATUS

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    PPBC_DEVICE  pDevice  = GetDeviceContext(SpbController);
    PPBC_TARGET  pTarget  = GetTargetContext(SpbTarget);
    PPBC_REQUEST pRequest = GetRequestContext(SpbRequest);
    BOOLEAN completeRequest = FALSE;
    
    NT_ASSERT(pDevice  != NULL);
    NT_ASSERT(pTarget  != NULL);
    NT_ASSERT(pRequest != NULL);

    UNREFERENCED_PARAMETER(Length);

    NTSTATUS status;
    
    //
    // Get the request parameters.
    //

    SPB_REQUEST_PARAMETERS params;
    SPB_REQUEST_PARAMETERS_INIT(&params);
    SpbRequestGetParameters(SpbRequest, &params);

    //
    // Initialize request context.
    //
    
    pRequest->SpbRequest = SpbRequest;
    pRequest->Type = params.Type;
    pRequest->SequencePosition = params.Position;
    pRequest->TotalInformation = 0;
    pRequest->TransferCount = 1;
    pRequest->TransferIndex = 0;
    pRequest->bIoComplete = FALSE;

    //
    // Validate the request before beginning the transfer.
    //
    
    status = PbcRequestValidate(pRequest);

    if (!NT_SUCCESS(status))
    {
        goto exit;
    }
    
    //
    // Configure the request.
    //

    status = PbcRequestConfigureForIndex(
        pRequest, 
        pRequest->TransferIndex);

    if (!NT_SUCCESS(status))
    {
        Trace(
            TRACE_LEVEL_ERROR, 
            TRACE_FLAG_SPBDDI, 
            "Error configuring request context for SPBREQUEST %p (SPBTARGET %p)"
            "- %!STATUS!", 
            pRequest->SpbRequest,
            SpbTarget,
            status);

        goto exit;
    }

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    //
    // Mark request cancellable (if cancellation supported).
    //

    status = WdfRequestMarkCancelableEx(
        pRequest->SpbRequest, OnCancel);

    if (!NT_SUCCESS(status))
    {
        //
        // WdfRequestMarkCancelableEx should only fail if the request
        // has already been cancelled. If it does fail the request
        // must be completed with the corresponding status.
        //

        NT_ASSERTMSG("WdfRequestMarkCancelableEx should only fail if the request has already been cancelled",
            status == STATUS_CANCELLED);

        Trace(
            TRACE_LEVEL_INFORMATION,
            TRACE_FLAG_TRANSFER,
            "Failed to mark SPBREQUEST %p cancellable - %!STATUS!",
            pRequest->SpbRequest,
            status);

        WdfSpinLockRelease(pDevice->Lock);
        goto exit;
    }
    
    //
    // If sequence position is...
    //   - single:     ensure there is not a current target
    //   - not single: ensure that the current target is the
    //                 same as this target
    //
    
    if (params.Position == SpbRequestSequencePositionSingle)
    {        
        NT_ASSERT(pDevice->pCurrentTarget == NULL);
    }
    else
    {   
        NT_ASSERT(pDevice->pCurrentTarget == pTarget);
    }
    
    //
    // Ensure there is not a current request.
    //
    
    NT_ASSERT(pTarget->pCurrentRequest == NULL);
    
    //
    // Update the device and target contexts.
    //
    
    if (pRequest->SequencePosition == SpbRequestSequencePositionSingle)
    {
        pDevice->pCurrentTarget = pTarget;
    }
    
    pTarget->pCurrentRequest = pRequest;
    
    //
    // Configure controller and kick-off read.
    // Request will be completed asynchronously.
    //
    
    PbcRequestDoTransfer(pDevice, pRequest);
    
    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. This must be done outside of 
    //       the locked code.
    if (pRequest->bIoComplete)
    {
        completeRequest = TRUE;
    }

    WdfSpinLockRelease(pDevice->Lock);
    
    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. This must be done outside of 
    //       the locked code.
    if (completeRequest)
    {
        PbcRequestComplete(pRequest);
    }

exit:
    
    if (!NT_SUCCESS(status))
    {
        SpbRequestComplete(SpbRequest, status);
    }
    
    FuncExit(TRACE_FLAG_TRANSFER);
}

NTSTATUS
PbcRequestConfigureForIndex(
    _Inout_  PPBC_REQUEST            pRequest,
    _In_     ULONG                   Index
    )
/*++
 
  Routine Description:

    This is a helper routine used to configure the request
    context and controller hardware for a transfer within a 
    sequence. It validates parameters and retrieves
    the transfer buffer as necessary.

  Arguments:

    pRequest - a pointer to the PBC request context
    Index - index of the transfer within the sequence

  Return Value:

    STATUS

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    NT_ASSERT(pRequest != NULL);
 
    NTSTATUS status = STATUS_SUCCESS;

    //
    // Get transfer parameters for index.
    //

    SPB_TRANSFER_DESCRIPTOR descriptor;
    PMDL pMdl;
    
    SPB_TRANSFER_DESCRIPTOR_INIT(&descriptor);

    SpbRequestGetTransferParameters(
        pRequest->SpbRequest, 
        Index, 
        &descriptor, 
        &pMdl);
       
    NT_ASSERT(pMdl != NULL);
    
    //
    // Configure request context.
    //

    pRequest->pMdlChain = pMdl;
    pRequest->Length = descriptor.TransferLength;
    pRequest->Information = 0;
    pRequest->Direction = descriptor.Direction;
    pRequest->DelayInUs = descriptor.DelayInUs;

    //
    // Update sequence position if request is type sequence.
    //

    if (pRequest->Type == SpbRequestTypeSequence)
    {
        if   (pRequest->TransferCount == 1)
        {
            pRequest->SequencePosition = SpbRequestSequencePositionSingle;
        }
        else if (Index == 0)
        {
            pRequest->SequencePosition = SpbRequestSequencePositionFirst;
        }
        else if (Index == (pRequest->TransferCount - 1))
        {
            pRequest->SequencePosition = SpbRequestSequencePositionLast;
        }
        else
        {
            pRequest->SequencePosition = SpbRequestSequencePositionContinue;
        }
    }

    PPBC_TARGET pTarget = GetTargetContext(SpbRequestGetTarget(pRequest->SpbRequest));
    NT_ASSERT(pTarget != NULL);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_TRANSFER,
        "Request context configured for %s (index %lu) "
        "to address 0x%lx (SPBTARGET %p)",
        pRequest->Direction == SpbTransferDirectionFromDevice ? "read" : "write",
        Index,
        pTarget->Settings.Address,
        pTarget->SpbTarget);

    FuncExit(TRACE_FLAG_TRANSFER);

    return status;
}

VOID
PbcRequestDoTransfer(
    _In_     PPBC_DEVICE             pDevice,
    _In_     PPBC_REQUEST            pRequest
    )
/*++
 
  Routine Description:

    This routine either starts the delay timer or
    kicks off the transfer depending on the request
    parameters.

  Arguments:

    pDevice - a pointer to the PBC device context
    pRequest - a pointer to the PBC request context

  Return Value:

    None. The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);
    
    NT_ASSERT(pDevice  != NULL);
    NT_ASSERT(pRequest != NULL);

    //
    // Start delay timer if necessary for this request,
    // otherwise continue transfer. 
    //
    // NOTE: Note using a timer to implement IO delay is only 
    //       applicable for sufficiently long delays (> 15ms).
    //       For shorter delays, especially on the order of
    //       microseconds, consider using a different mechanism.
    //

    if (pRequest->DelayInUs > 0)
    {
        Trace(
            TRACE_LEVEL_INFORMATION,
            TRACE_FLAG_TRANSFER,
            "Delaying %lu us before configuring transfer for WDFDEVICE %p",
            pRequest->DelayInUs,
            pDevice->FxDevice);

        BOOLEAN bTimerAlreadyStarted;

        bTimerAlreadyStarted = WdfTimerStart(
            pDevice->DelayTimer, 
            WDF_REL_TIMEOUT_IN_US(pRequest->DelayInUs));

        //
        // There should never be another request
        // scheduled for delay.
        //

        if (bTimerAlreadyStarted == TRUE)
        {
            Trace(
                TRACE_LEVEL_ERROR,
                TRACE_FLAG_TRANSFER,
                "The delay timer should not be started");
        }
    }
    else
    {
        ControllerConfigureForTransfer(pDevice, pRequest);
    }

    FuncExit(TRACE_FLAG_TRANSFER);
}

VOID
OnDelayTimerExpired(
    _In_  WDFTIMER  Timer
    )
/*++
 
  Routine Description:

    This routine is invoked whenever the driver's delay
    timer expires. It kicks off the transfer for the request.

  Arguments:

    Timer - a handle to a framework timer object

  Return Value:

    None.

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    WDFDEVICE fxDevice;
    PPBC_DEVICE pDevice;
    PPBC_TARGET pTarget = NULL;
    PPBC_REQUEST pRequest = NULL;
    BOOLEAN completeRequest = FALSE;
    
    fxDevice = (WDFDEVICE) WdfTimerGetParentObject(Timer);
    pDevice = GetDeviceContext(fxDevice);

    NT_ASSERT(pDevice != NULL);

    //
    // Acquire the device lock.
    //

    WdfSpinLockAcquire(pDevice->Lock);

    //
    // Make sure the target and request are
    // still valid.
    //

    pTarget = pDevice->pCurrentTarget;
    
    if (pTarget == NULL)
    {
        Trace(
            TRACE_LEVEL_WARNING,
            TRACE_FLAG_TRANSFER,
            "Delay timer expired without a valid current target for WDFDEVICE %p, "
            "this should only occur if the request was already completed",
            pDevice->FxDevice);

        goto exit;
    }
    
    pRequest = pTarget->pCurrentRequest;

    if (pRequest == NULL)
    {
        Trace(
            TRACE_LEVEL_WARNING,
            TRACE_FLAG_TRANSFER,
            "Delay timer expired without a valid current request for SPBTARGET %p, "
            "this should only occur if the request was already cancelled",
            pTarget->SpbTarget);

        goto exit;
    }

    NT_ASSERT(pRequest->SpbRequest != NULL);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_TRANSFER,
        "Delay timer expired, ready to configure transfer for WDFDEVICE %p",
        pDevice->FxDevice);

    ControllerConfigureForTransfer(pDevice, pRequest);
    
    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. This must be done outside of 
    //       the locked code.
    if (pRequest->bIoComplete)
    {
        completeRequest = TRUE;
    }

exit:

    //
    // Release the device lock.
    //

    WdfSpinLockRelease(pDevice->Lock);
    
    // TODO: Remove this block. For the purpose of this 
    //       skeleton sample, simply complete the request 
    //       synchronously. This must be done outside of 
    //       the locked code.
    if (completeRequest)
    {
        PbcRequestComplete(pRequest);
    }

    FuncExit(TRACE_FLAG_TRANSFER);
}

VOID
PbcRequestComplete(
    _In_     PPBC_REQUEST            pRequest
    )
/*++
 
  Routine Description:

    This routine completes the SpbRequest associated with
    the PBC_REQUEST context.

  Arguments:

    pRequest - a pointer to the PBC request context

  Return Value:

    None. The request is completed asynchronously.

--*/
{
    FuncEntry(TRACE_FLAG_TRANSFER);

    NT_ASSERT(pRequest != NULL);

    Trace(
        TRACE_LEVEL_INFORMATION,
        TRACE_FLAG_TRANSFER,
        "Completing SPBREQUEST %p with %!STATUS!, transferred %Iu bytes",
        pRequest->SpbRequest,
        pRequest->Status,
        pRequest->TotalInformation);

    WdfRequestSetInformation(
        pRequest->SpbRequest,
        pRequest->TotalInformation);

    SpbRequestComplete(
        pRequest->SpbRequest, 
        pRequest->Status);

    FuncExit(TRACE_FLAG_TRANSFER);
}

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