/*++

Copyright (C) Microsoft. All rights reserved.

Module Name:

    io.c

Abstract:

    This file contains routines to handle all IO operations on a GPIO
    controller.


Environment:

    Kernel mode

--*/

//
// ------------------------------------------------------------------- Includes
//

#include "pch.h"

#if defined(EVENT_TRACING)
#include "io.tmh"               // auto-generated by WPP
#endif

#include "clientinvk.h"
#include "hub.h"

//
// -------------------------------------------------------------------- Pragmas
//

#pragma alloc_text(PAGE, GpiopProcessControllerSpecificFunctionIoctl)
#pragma alloc_text(PAGE, GpiopProcessFunctionConfigPinsCommitRequest)

//
// ------------------------------------------------------------------ Functions
//

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopProcessControllerSpecificFunctionIoctl (
    __in PDEVICE_EXTENSION GpioExtension,
    __in WDFQUEUE Queue,
    __in WDFREQUEST Request,
    __out PULONG BytesReadWritten
    )

/*++

Routine Description:

    This routine is a hander for IOCTL_GPIO_CONTROLLER_SPECIFIC_FUNCTION. This
    routine figures out the parameters of the request (e.g. the buffer, the
    size) and calls into the client driver to service the IOCTL.

    N.B. Since the IOCTL is buffered, WdfRequestRetrieveOutputBuffer() &
         WdfRequestRetrieveInputBuffer() return the same buffer pointer. So
         all the information from the buffer must be read prior to writing
         to it.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    Queue -  Supplies a handle to the framework queue object that is associated
        with the I/O request.

    Request - Supplies a handle to a framework request object.

    BytesReadWritten - Supplies a pointer to a variable that receives the total
        number of bytes written.

Return Value:

  NTSTATUS code.

--*/

{

    PCLIENT_CONTROLLER_QUERY_SET_INFORMATION_OUTPUT BankMapping;
    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    WDFFILEOBJECT FileObject;
    PGPIO_BANK_ENTRY GpioBank;
    BANK_ID Index;
    PVOID InputBuffer;
    size_t InputBufferLength;
    PBOOLEAN Mapping;
    PVOID OutputBuffer;
    size_t OutputBufferLength;
    BOOLEAN Result;
    ULONG Size;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(Queue);

    PAGED_CODE();

    //
    // Retrieve the file context associated with this request.
    //

    BankMapping = NULL;
    FileContext = NULL;
    FileObject = WdfRequestGetFileObject(Request);
    if (FileObject != NULL) {
        FileContext = GpioClxGetFileObjectContext(FileObject);
    }

    if (FileContext == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "%s: Failed to get file context from request! "
                    "Request = %p\n",
                    __FUNCTION__,
                    Request);

        Status = STATUS_UNSUCCESSFUL;
        goto ProcessControllerSpecificFunctionIoctlEnd;
    }

    //
    // Retrieve the input buffer (if any) provided by the caller.
    //

    *BytesReadWritten = 0x0;
    Status = WdfRequestRetrieveInputBuffer(Request,
                                           0,
                                           &InputBuffer,
                                           &InputBufferLength);


    //
    // If the input buffer (optional) is not supplied by the caller, then
    // WdfRequestRetrieveInputBuffer() still fails with STATUS_BUFFER_TOO_SMALL
    // even though the required minimum size was specified to be zero.
    //

    if (!NT_SUCCESS(Status) && (Status != STATUS_BUFFER_TOO_SMALL)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "%s: WdfRequestRetrieveInputBuffer() failed! "
                    "Status = %#x\n",
                    __FUNCTION__,
                    Status);

        goto ProcessControllerSpecificFunctionIoctlEnd;
    }

    //
    // Retrieve the output buffer (if any) provided by the caller.
    //

    Status = WdfRequestRetrieveOutputBuffer(Request,
                                            0,
                                            &OutputBuffer,
                                            &OutputBufferLength);

    //
    // If the output buffer (optional) is not supplied by the caller, then
    // WdfRequestRetrieveOutputBuffer() still fails with STATUS_BUFFER_TOO_SMALL
    // even though the required minimum size was specified to be zero.
    //

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "%s: WdfRequestRetrieveOutputBuffer() failed! "
                    "Status = %#x\n",
                    __FUNCTION__,
                    Status);

        goto ProcessControllerSpecificFunctionIoctlEnd;
    }

    //
    // Allocate an output buffer for the query. Two buffers are allocated for
    // banks information; the second buffer is used as a scratch buffer
    // by the validation routine.
    //

    Size = FIELD_OFFSET(CLIENT_CONTROLLER_QUERY_SET_INFORMATION_OUTPUT,
                        ControllerFunctionBankMapping.Mapping) +
        GpioExtension->TotalBanks * sizeof(BOOLEAN);

    BankMapping = GPIO_ALLOCATE_POOL(PagedPool, Size);
    if (BankMapping == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "%s: Failed to allocate memory for IOCTL-bank mapping!\n",
                    __FUNCTION__);

        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto ProcessControllerSpecificFunctionIoctlEnd;
    }

    RtlZeroMemory(BankMapping, Size);
    BankMapping->Size = (USHORT)Size;

    //
    // Query the IOCTL <-> bank mapping from the client driver.
    //

    Status = GpioClnInvokeQueryControllerFunctionBankMapping(
                 GpioExtension,
                 InputBuffer,
                 InputBufferLength,
                 OutputBufferLength,
                 BankMapping);

    //
    // If IOCTL <-> bank mapping query succeeded, then validate the output
    // returned by the client driver.
    //

    if (NT_SUCCESS(Status)) {
        Result = GpiopValidateControllerFunctionBankMappings(GpioExtension,
                                                             BankMapping);

        if (Result == FALSE) {
            Status = STATUS_UNSUCCESSFUL;
        }

    //
    // If the client driver did not supply a handler for this query, then
    // assume default mappings:
    //     If the request is sent against a handle associated with some pins,
    //     then assume only the corresponding bank needs to be active.
    //     Otherwise, assume that all banks need to be active for this request.
    //

    } else if ((Status == STATUS_NOT_IMPLEMENTED) ||
               (Status == STATUS_NOT_SUPPORTED)) {

        Mapping = &BankMapping->ControllerFunctionBankMapping.Mapping[0];
        if ((FileContext != NULL) && (FileContext->PinCount > 0)) {
            Mapping[FileContext->BankId] = TRUE;

        } else {
            for (Index = 0; Index < GpioExtension->TotalBanks; Index += 1) {
                Mapping[Index] = TRUE;
            }
        }

        Status = STATUS_SUCCESS;
    }

    //
    // If the IOCTL <-> bank mapping query failed or returned incorrect
    // information, then fail the IOCTL request.
    //

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "%s: Query IOCTL-bank mapping failed or the query returned "
                    "invalid information!\n",
                    __FUNCTION__);

        goto ProcessControllerSpecificFunctionIoctlEnd;
    }

    //
    // Take a power reference on the required banks/components. This will
    // prevent the PEP from transitioning the bank to F1 while the IOCTL is
    // being serviced.
    //
    // Also, acquire the passive-level controller on all the required banks.
    // This is done to serialize requests sent to the client driver.
    //
    // N.B. Since the loop always progresses from bank 0 -> N for all IOCTLs,
    //      there is no deadlock issue.
    //

    Mapping = &BankMapping->ControllerFunctionBankMapping.Mapping[0];
    for (Index = 0; Index < GpioExtension->TotalBanks; Index += 1) {
        if (Mapping[Index] == FALSE) {
            continue;
        }

        PoFxActivateComponent(GpioExtension->PowerHandle, Index, 0);

        GpioBank = GpiopGetBankEntry(GpioExtension, Index);

        GPIO_ASSERT(GpioBank != NULL);

        _Analysis_assume_lock_not_held_((GpioBank->PassiveLock)); // "GpioBank" is different in each iteration of the loop
        GPIO_ACQUIRE_BANK_LOCK(GpioBank);
    }

    //
    // Call into the GPIO client driver to service the private IOCTL.
    //

    Status = GpioClnInvokeControllerSpecificFunctionHandler(
                 GpioExtension,
                 InputBuffer,
                 InputBufferLength,
                 OutputBuffer,
                 OutputBufferLength,
                 BytesReadWritten);

    //
    // Release the passive-level controller on all the required banks.
    //
    // Also drop the power reference that was previously taken on the banks.
    //

    for (Index = 0; Index < GpioExtension->TotalBanks; Index += 1) {
        if (Mapping[Index] == FALSE) {
            continue;
        }

        GpioBank = GpiopGetBankEntry(GpioExtension, Index);

        GPIO_ASSERT(GpioBank != NULL);

        GPIO_RELEASE_BANK_LOCK(GpioBank);

        PoFxIdleComponent(GpioExtension->PowerHandle, Index, 0);
    }

ProcessControllerSpecificFunctionIoctlEnd:
    if (BankMapping != NULL) {
        GPIO_FREE_POOL(BankMapping);
    }

    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopProcessFunctionConfigPinsCommitRequest(
    __in PDEVICE_EXTENSION GpioExtension,
    __in WDFQUEUE Queue,
    __in WDFREQUEST Request,
    __out PULONG BytesRead
    )

/*++

Routine Description:

    This routine is a hander for IOCTL_GPIO_COMMIT_FUNCTION_CONFIG_PINS.
    This routine figures out the parameters of the request and calls into
    the class extension to configure the right pins for function config.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    Queue -  Supplies a handle to the framework queue object that is associated
        with the commit request.

    Request - Supplies a handle to a framework request object.

    BytesRead - Supplies a pointer to a variable that receives the total
        number of bytes read.

Return Value:

  NTSTATUS code.

--*/

{

    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    WDFFILEOBJECT FileObject;
    WDF_REQUEST_PARAMETERS RequestParameters;
    NTSTATUS Status;

    PAGED_CODE();

    UNREFERENCED_PARAMETER(Queue);

    Status = STATUS_SUCCESS;
    FileContext = NULL;
    WDF_REQUEST_PARAMETERS_INIT(&RequestParameters);
    WdfRequestGetParameters(Request, &RequestParameters);
    FileObject = WdfRequestGetFileObject(Request);
    if (FileObject != NULL) {
        FileContext = GpioClxGetFileObjectContext(FileObject);
    }

    if (FileContext == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_FUNCCONFIG,
                    "%s: Failed to get file"
                    " context from request! Request = %p\n",
                    __FUNCTION__,
                    Request);

        Status = STATUS_NOT_SUPPORTED;
        goto ProcessFunctionConfigPinsCommitRequestEnd;
    }

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_FUNCCONFIG,
                    "%s: WdfRequestRetrieveOutputBuffer() failed! "
                    "Status = %#x\n",
                    __FUNCTION__,
                    Status);

        goto ProcessFunctionConfigPinsCommitRequestEnd;
    }

    Status = GpiopConnectFunctionConfigPinsCommit(GpioExtension, FileContext);
    *BytesRead = 0;

ProcessFunctionConfigPinsCommitRequestEnd:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopProcessReadPinsRequest (
    __in PDEVICE_EXTENSION GpioExtension,
    __in WDFQUEUE Queue,
    __in WDFREQUEST Request,
    __out PULONG BytesRead
    )

/*++

Routine Description:

    This routine is a hander for IOCTL_GPIO_READ_PINS. This routine figures out
    the parameters of the request (e.g. the buffer, size, file context) and
    calls into the class extension to read from those pins.

    Note this routine is not marked PAGED as it may be called before/after
    the boot device is in D0/D3 if boot device has GPIO dependencies.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    Queue -  Supplies a handle to the framework queue object that is associated
        with the I/O request.

    Request - Supplies a handle to a framework request object.

    BytesRead - Supplies a pointer to a variable that receives the total
        number of bytes read.

Return Value:

  NTSTATUS code.

--*/

{

    ULONG BitsRead;
    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    WDFFILEOBJECT FileObject;
    PVOID OutputBuffer;
    size_t OutputBufferLength;
    WDF_REQUEST_PARAMETERS RequestParameters;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(Queue);

    WDF_REQUEST_PARAMETERS_INIT(&RequestParameters);

    WdfRequestGetParameters(Request, &RequestParameters);

    FileContext = NULL;
    FileObject = WdfRequestGetFileObject(Request);
    if (FileObject != NULL) {
        FileContext = GpioClxGetFileObjectContext(FileObject);
    }

    if (FileContext == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "GpiopProcessReadPinsRequest: Failed to get file context"
                    "from request! Request = %p\n",
                    Request);

        Status = STATUS_NOT_SUPPORTED;
        goto ProcessReadPinsRequestEnd;
    }

    //
    // N.B. Since the IOCTL is buffered, WdfRequestRetrieveOutputBuffer &
    //      WdfRequestRetrieveInputBuffer return the same buffer pointer. So
    //      all the information from the buffer must be read prior to writing
    //      to it.
    //

    Status = WdfRequestRetrieveOutputBuffer(Request,
                                            sizeof(UCHAR),
                                            &OutputBuffer,
                                            &OutputBufferLength);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "GpiopProcessReadPinsRequest: "
                    "WdfRequestRetrieveOutputBuffer() failed! Status = %#x\n",
                    Status);

        goto ProcessReadPinsRequestEnd;
    }

    //
    // Perform a read from the specified pins.
    //

    if (OutputBufferLength > MAX_ULONG / 8) {
        OutputBufferLength = MAX_ULONG / 8;
    }

    Status = GpiopReadPins(GpioExtension,
                           FileContext,
                           GPIO_IO_FLAG_NONE,
                           OutputBuffer,
                           (ULONG)(OutputBufferLength * 8),
                           &BitsRead);

    *BytesRead = (BitsRead + 7) / 8;

ProcessReadPinsRequestEnd:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopProcessWritePinsRequest (
    __in PDEVICE_EXTENSION GpioExtension,
    __in WDFQUEUE Queue,
    __in WDFREQUEST Request,
    __out PULONG BytesWritten
    )

/*++

Routine Description:

    This routine is a hander for IOCTL_GPIO_WRITE_PINS. This routine figures out
    the parameters of the request (e.g. the buffer, the size, file context) and
    calls into the class extension to write to those pins.

    Note this routine is not marked PAGED as it may be called before/after
    the boot device is in D0/D3 if boot device has GPIO dependencies.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    Queue -  Supplies a handle to the framework queue object that is associated
        with the I/O request.

    Request - Supplies a handle to a framework request object.

    BytesWritten - Supplies a pointer to a variable that receives the total
        number of bytes written.

Return Value:

  NTSTATUS code.

--*/

{

    ULONG BitsWritten;
    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    WDFFILEOBJECT FileObject;
    PVOID InputBuffer;
    size_t InputBufferLength;
    WDF_REQUEST_PARAMETERS RequestParameters;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(Queue);

    WDF_REQUEST_PARAMETERS_INIT(&RequestParameters);

    WdfRequestGetParameters(Request, &RequestParameters);

    FileContext = NULL;
    FileObject = WdfRequestGetFileObject(Request);
    if (FileObject != NULL) {
        FileContext = GpioClxGetFileObjectContext(FileObject);
    }

    if (FileContext == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "GpiopProcessWritePinsRequest: Failed to get file context"
                    "from request! Request = %p\n",
                    Request);

        Status = STATUS_NOT_SUPPORTED;
        goto ProcessWritePinsRequestEnd;
    }

    //
    // N.B. Since the IOCTL is buffered, WdfRequestRetrieveOutputBuffer &
    //      WdfRequestRetrieveInputBuffer return the same buffer pointer. So
    //      all the information from the buffer must be read prior to writing
    //      to it.
    //

    Status = WdfRequestRetrieveInputBuffer(Request,
                                           sizeof(UCHAR),
                                           &InputBuffer,
                                           &InputBufferLength);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IO,
                    "GpiopProcessWritePinsRequest: "
                    "WdfRequestRetrieveInputBuffer() failed! Status = %#x\n",
                    Status);

        goto ProcessWritePinsRequestEnd;
    }

    //
    // Perform a write to the specified pins.
    //

    if (InputBufferLength > MAX_ULONG / 8) {
        InputBufferLength = MAX_ULONG / 8;
    }

    Status = GpiopWritePins(GpioExtension,
                            FileContext,
                            Request,
                            GPIO_IO_FLAG_NONE,
                            InputBuffer,
                            (ULONG)(InputBufferLength * 8),
                            &BitsWritten);

    *BytesWritten = (BitsWritten + 7) / 8;

ProcessWritePinsRequestEnd:
    return Status;
}

_Function_class_(GPIO_FAST_IO_READ_PINS)
_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
GpioProcessFastIoReadPins (
    _In_ PGPIO_FAST_IO_READ_PARAMS ReadParameters
)

/*++

Routine Description:

    This routine reads from the pin specified by the I/O parameters.

    Note this routine is not marked PAGED as it may be called before/after
    the boot device is in D0/D3 if boot device has GPIO dependencies.

    Also note that this routine must only be called within the bounds of the
    open and close for the file handle associated with the pins that are being
    read from. Thus, the FileContext must always be valid when this function is
    called.

Arguments:

    ReadParameters - Supplies a pointer to the I/O parameters for the GPIO pins.

Return Value:

    NTSTATUS code.

--*/

{

    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    PDEVICE_EXTENSION GpioExtension;

    NON_PAGED_PASSIVE_LEVEL_CODE();

    FileContext = (PGPIO_FILE_OBJECT_CONTEXT)ReadParameters->GpioContext;

    GPIO_ASSERT(FileContext->FastIoInterfaceReferenceCount > 0);

    GpioExtension = FileContext->GpioExtension;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    return GpiopReadPins(GpioExtension,
                         FileContext,
                         GPIO_IO_FLAG_FAST_IO_MODE,
                         ReadParameters->Buffer,
                         ReadParameters->BufferSizeInBits,
                         &ReadParameters->BitsRead);
}

_Function_class_(GPIO_FAST_IO_WRITE_PINS)
_IRQL_requires_(PASSIVE_LEVEL)
NTSTATUS
GpioProcessFastIoWritePins (
    _In_ PGPIO_FAST_IO_WRITE_PARAMS WriteParameters
)

/*++

Routine Description:

    This routine writes from the pin specified by the ReadParameters parameter.

    Note this routine is not marked PAGED as it may be called before/after
    the boot device is in D0/D3 if boot device has GPIO dependencies.

    Also note that this routine must only be called within the bounds of the
    open and close for the file handle associated with the pins that are being
    written to. Thus, the FileContext must always be valid when this function is
    called.

Arguments:

    WriteParameters - Supplies a pointer to the I/O parameters for the GPIO
        pins.

Return Value:

    NTSTATUS code.

--*/

{

    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    PDEVICE_EXTENSION GpioExtension;

    NON_PAGED_PASSIVE_LEVEL_CODE();

    FileContext = (PGPIO_FILE_OBJECT_CONTEXT)WriteParameters->GpioContext;

    GPIO_ASSERT(FileContext->FastIoInterfaceReferenceCount > 0);

    GpioExtension = FileContext->GpioExtension;

    GPIO_CLX_VALIDATE_SIGNATURE(GpioExtension);

    return GpiopWritePins(GpioExtension,
                          FileContext,
                          NULL,
                          GPIO_IO_FLAG_FAST_IO_MODE,
                          WriteParameters->Buffer,
                          WriteParameters->BufferSizeInBits,
                          &WriteParameters->BitsWritten);
}

VOID
GpioFastIoInterfaceReference (
   _In_ PVOID Context
   )

/*++

Routine Description:

    This routine increments the reference count on the GPIO fast I/O interface.
    This count is checked when the handle is closed for the GPIO pin to
    ensure that there are no outstanding open references.

Arguments:

    Context - Supplies a pointer to the context for the GPIO pin.

Return Value:

    None.

--*/

{

    InterlockedIncrement(
        &((PGPIO_FILE_OBJECT_CONTEXT)Context)->FastIoInterfaceReferenceCount);

    return;
}

VOID
GpioFastIoInterfaceDereference (
   _In_ PVOID Context
   )

/*++

Routine Description:

    This routine decrements the reference count on the GPIO fast I/O interface.
    This count is checked when the handle is closed for the GPIO pin to
    ensure that there are no outstanding open references.

Arguments:

    Context - Supplies a pointer to the context for the GPIO pin.

Return Value:

    None.

--*/

{

    PGPIO_FILE_OBJECT_CONTEXT FileContext;

    FileContext = (PGPIO_FILE_OBJECT_CONTEXT)Context;

    GPIO_ASSERT(FileContext->FastIoInterfaceReferenceCount > 0);

    InterlockedDecrement(&FileContext->FastIoInterfaceReferenceCount);
    return;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopProcessFastIoInterfaceRequest (
    __in PDEVICE_EXTENSION GpioExtension,
    __in WDFQUEUE Queue,
    __in WDFREQUEST Request,
    __out PULONG BytesRead
    )

/*++

Routine Description:

    This routine is a hander for IOCTL_GPIO_QUERY_FAST_IO_INTERFACE. This
    routine provides an interface that contains the fast I/O read and write
    methoods along with a context that goes along with those methods. The
    interface also contains methods that reference and dereference the
    interface.

    This interface is only supported for on-SoC GPIO controllers. The IOCTL will
    fail and not return an interface for off-SoC GPIO controllers.

    Note this routine is not marked PAGED as it may be called before/after
    the boot device is in D0/D3 if boot device has GPIO dependencies.

Arguments:

    GpioExtension - Supplies a pointer to the GPIO's device extension.

    Queue -  Supplies a handle to the framework queue object that is associated
        with the I/O request.

    Request - Supplies a handle to a framework request object.

    BytesRead - Supplies a pointer to a variable that receives the total
        number of bytes read.

Return Value:

    NTSTATUS code.

--*/

{

    PGPIO_FILE_OBJECT_CONTEXT FileContext;
    WDFFILEOBJECT FileObject;
    PGPIO_FAST_IO_INTERFACE InterfaceBuffer;
    size_t OutputBufferLength;
    WDF_REQUEST_PARAMETERS RequestParameters;
    USHORT Size;
    NTSTATUS Status;

    UNREFERENCED_PARAMETER(Queue);

    WDF_REQUEST_PARAMETERS_INIT(&RequestParameters);
    WdfRequestGetParameters(Request, &RequestParameters);
    FileContext = NULL;
    FileObject = WdfRequestGetFileObject(Request);
    if (FileObject != NULL) {
        FileContext = GpioClxGetFileObjectContext(FileObject);
    }

    if (FileContext == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IOCTL,
                    "GpiopProcessFastIoInterfaceRequest: Failed to get file "
                    "context from request! Request = %p\n",
                    Request);

        Status = STATUS_INVALID_HANDLE;
        goto ProcessFastIoInterfaceRequestEnd;
    }

    //
    // Off-SoC GPIO controllers will not benefit from fast I/O optimizations.
    // Force such I/Os to be dispatch along the normal I/O path.
    //

    if (GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) != FALSE) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IOCTL,
                    "GpiopProcessFastIoInterfaceRequest: Fast I/O interface is "
                    "returned only for on-SoC GPIO controllers! Request = %p\n",
                    Request);

        Status = STATUS_NOT_SUPPORTED;
        goto ProcessFastIoInterfaceRequestEnd;
    }

    //
    // N.B. Since the IOCTL is buffered, WdfRequestRetrieveOutputBuffer &
    //      WdfRequestRetrieveInputBuffer return the same buffer pointer. So
    //      all the information from the buffer must be read prior to writing
    //      to it.
    //

    Size = sizeof(GPIO_FAST_IO_INTERFACE);
    Status = WdfRequestRetrieveOutputBuffer(Request,
                                            Size,
                                            &InterfaceBuffer,
                                            &OutputBufferLength);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_IOCTL,
                    "GpiopProcessFastIoInterfaceRequest: "
                    "WdfRequestRetrieveOutputBuffer() failed! Status = %#x\n",
                    Status);

        goto ProcessFastIoInterfaceRequestEnd;
    }

    //
    // Fill out the interface.
    //

    RtlZeroMemory(InterfaceBuffer, Size);
    InterfaceBuffer->Header.Size = Size;
    InterfaceBuffer->Header.Version = GPIO_FAST_IO_INTERFACE_VERSION;
    InterfaceBuffer->Header.InterfaceReference = GpioFastIoInterfaceReference;
    InterfaceBuffer->Header.InterfaceDereference = GpioFastIoInterfaceDereference;
    InterfaceBuffer->GpioFastIoReadPins = GpioProcessFastIoReadPins;
    InterfaceBuffer->GpioFastIoWritePins = GpioProcessFastIoWritePins;
    InterfaceBuffer->GpioContext = FileContext;
    *BytesRead = Size;

    //
    // Acquire the initial reference on the interface. Per MSDN:
    // "A driver that exports the interface is responsible for calling
    //  InterfaceReference to increment the reference count before the driver
    //  exports the interface."
    //
    // The caller is responsible for dereferencing the interface prior to
    // closing the associated handle.
    //

    GpioFastIoInterfaceReference(FileContext);

ProcessFastIoInterfaceRequestEnd:
    return Status;
}

