/*++

Copyright (C) Microsoft. All rights reserved.

Module Name:

    client.c

Abstract:

    This file contains interfaces exported by the class extension for client
    driver to call.


Environment:

    Kernel mode

--*/

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

#include "pch.h"

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

#include "client.h"
#include "hub.h"

//
// -------------------------------------------------------------------- Defines
//

#define GPIO_DEVICE_NAME_FORMAT L"\\Device\\GPIO_%d"
#define GPIO_DEVICE_NAME_ECOUNT (13)                    // for SAL annotation

//
// ------------------------------------------------------------------ Globals
//

const PGPIO_CLX_EXPORTED_INTERFACES GpioClxExports[GPIO_CLX_TOTAL_EXPORTS] =
{
    (PGPIO_CLX_EXPORTED_INTERFACES) GpioClxRegisterClient,
    (PGPIO_CLX_EXPORTED_INTERFACES) GpioClxUnregisterClient,
    (PGPIO_CLX_EXPORTED_INTERFACES) GpioClxProcessAddDevicePreDeviceCreate,
    (PGPIO_CLX_EXPORTED_INTERFACES) GpioClxProcessAddDevicePostDeviceCreate,
    (PGPIO_CLX_EXPORTED_INTERFACES) GpioClxAcquireInterruptLock,
    (PGPIO_CLX_EXPORTED_INTERFACES) GpioClxReleaseInterruptLock
};

//
// ----------------------------------------------------------------- Prototypes
//

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopAssignTargetName (
    __in PWDFDEVICE_INIT DeviceInit,
    __deref_out_ecount_full(GPIO_DEVICE_NAME_ECOUNT + 2) __nullterminated
        PWCHAR *TargetNameOut
    );

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopQueryDeviceBiosName (
    __in WDFDEVICE Device,
    __in PDEVICE_EXTENSION GpioExtension
    );

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopSetupPrimaryDeviceIoQueue (
    __in PDEVICE_EXTENSION GpioExtension
    );

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopSetupSelfIoTarget (
    __in WDFDEVICE Device,
    __in PDEVICE_EXTENSION GpioExtension
    );

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

#pragma alloc_text(PAGE, GpioClxProcessAddDevicePreDeviceCreate)
#pragma alloc_text(PAGE, GpioClxProcessAddDevicePostDeviceCreate)
#pragma alloc_text(PAGE, GpioClxRegisterClient)
#pragma alloc_text(PAGE, GpioClxUnregisterClient)

//
// Internal routines.
//

#pragma alloc_text(PAGE, GpiopAssignTargetName)
#pragma alloc_text(PAGE, GpiopOpenResourceHubTarget)
#pragma alloc_text(PAGE, GpiopQueryDeviceBiosName)
#pragma alloc_text(PAGE, GpiopSetupPrimaryDeviceIoQueue)
#pragma alloc_text(PAGE, GpiopSetupSelfIoTarget)

//
// ------------------------------------------------------------------ Globals
//

ULONG GpioInstanceNumber = 0;

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

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxProcessAddDevicePreDeviceCreate (
    __in WDFDRIVER Driver,
    __inout PWDFDEVICE_INIT DeviceInit,
    __out PWDF_OBJECT_ATTRIBUTES FdoAttributes
    )

/*++

Routine Description:

    This export routine is called by the GPIO client driver when WDF
    invokes the client driver's AddDevice callback. The client driver invokes
    this routine prior to creating the device object. This routine is
    responsible for supplying the device prepare/release and entry/exit
    callbacks that WDF to transition the device into different states.

Arguments:

    Driver - Supplies a handle to the driver object created in DriverEntry.

    DeviceInit - Supplies a pointer to a framework-allocated WDFDEVICE_INIT
        structure.

    FdoAttributes - Supplies a pointer to a structure that receives the
        attributes to be supplied when the client driver creates its device
        object.

Return Value:

    NTSTATUS code.

--*/

{

    WDF_PNPPOWER_EVENT_CALLBACKS Callbacks;
    PGPIO_CLIENT_DRIVER Client;
    ULONG ContextSize;
    WDF_OBJECT_ATTRIBUTES FileAttributes;
    WDF_FILEOBJECT_CONFIG FileConfiguration;
    UCHAR IrpMinorFunction;
    ULONG MinimumSize;
    NTSTATUS Status;
    DECLARE_CONST_UNICODE_STRING(AccessRights, RESHUB_SDDL_DEVOBJ_SYS_ALL_ADM_ALL_UD_ALL);

    PAGED_CODE();

    Status = STATUS_SUCCESS;

    Client = GpiopFindClient(Driver);
    if (Client == NULL) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePreDeviceCreate: Failed to find "
                    "GPIO client driver entry in the list!\n");

        Status = STATUS_INVALID_PARAMETER;
        goto AddDeviceEnd;
    }

    //
    // Set PnP callbacks for prepare/release hardware and D0 entry/exit. All
    // callbacks not overridden here will be handled by the framework in the
    // default manner.
    //
    // N.B. The hub device is a special software-only device (and simply used
    //      to initialize the hub module). Thus the callbacks are not set for
    //      the hub device. The driver framework will handle the hub device
    //      in the default manner.
    //

    WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&Callbacks);
    Callbacks.EvtDeviceQueryStop = GpioClxEvtDeviceQueryStop;
    Callbacks.EvtDevicePrepareHardware = GpioClxEvtDevicePrepareHardware;
    Callbacks.EvtDeviceReleaseHardware = GpioClxEvtDeviceReleaseHardware;
    Callbacks.EvtDeviceD0Entry = GpioClxEvtDeviceD0Entry;
    Callbacks.EvtDeviceD0Exit = GpioClxEvtDeviceD0Exit;
    Callbacks.EvtDeviceD0ExitPreInterruptsDisabled =
                           GpioClxEvtDevicePreInterruptsDisabled;

    Callbacks.EvtDeviceD0EntryPostInterruptsEnabled =
                            GpioClxEvtDevicePostInterruptsEnabled;

    Callbacks.EvtDeviceSelfManagedIoInit = GpioClxEvtDeviceSelfManagedIoInit;
    Callbacks.EvtDeviceSelfManagedIoFlush = GpioClxEvtDeviceSelfManagedIoFlush;

    //
    // Register the PnP callbacks with the framework.
    //

    WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &Callbacks);

    //
    // Setup file create/close/cleanup callbacks. The create callback is
    // invoked when someone opens a handle to the device.
    //
    // The I/O Manager sends an IRP_MJ_CLEANUP request for a file object after
    // the last handle to the object is closed. When a driver receives an
    // IRP_MJ_CLEANUP request, the driver must complete any pending IRPs for
    // the specified file object. After the IRPs have been completed, the I/O
    // Manager destroys the file object. While the IRPs are pending, the I/O
    // Manager cannot delete the object. Since all IO is dispatched through the
    // default IO queue, and WDF automatically handles purging of pending
    // requests, a cleanup callback is not explicitly registered.
    //

    WDF_FILEOBJECT_CONFIG_INIT(&FileConfiguration,
                               GpioClxEvtDeviceFileCreate,
                               GpioClxEvtFileClose,
                               WDF_NO_EVENT_CALLBACK);

    //
    // Indicate that file object is optional. Note for all valid requests that
    // are sent to the GPIO stack, the file object will exist. However, it
    // may not exist in some weird cases. If there are child devices under
    // GPIO in ACPI namespace, then drivers for those devices may complete
    // create but decide to forward down close requests resulting in our
    // stack receiving a close with no matching create. Setting this flag
    // takes care of this.
    //

    FileConfiguration.FileObjectClass =
        (WdfFileObjectWdfCannotUseFsContexts | WdfFileObjectCanBeOptional);

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&FileAttributes,
                                            GPIO_FILE_OBJECT_CONTEXT);

    FileAttributes.SynchronizationScope = WdfSynchronizationScopeNone;

    WdfDeviceInitSetFileObjectConfig(DeviceInit,
                                     &FileConfiguration,
                                     &FileAttributes);

    //
    // Always assign a name to the device. The client driver can override this
    // if it wants to. The buffer needs to be held onto until the client driver
    // creates the device object.
    //

    Status = GpiopAssignTargetName(DeviceInit, &Client->TargetName);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePreDeviceCreate: Failed to assign "
                    "name to the GPIO device! Status = %#x, DeviceInit = %p\n",
                    Status,
                    DeviceInit);

        goto AddDeviceEnd;
    }

    //
    // Set the access permissions on the GPIO device. The GPIO stack uses
    // the same access rights as the Resource hub, which grants full-access to
    // SYSTEM and RW permissions to Local Service.
    //
    // N.B. For Local service clients, runtime checks will limit access to only
    //      UMDF drivers.
    //

    Status = WdfDeviceInitAssignSDDLString(DeviceInit, &AccessRights);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePreDeviceCreate: Failed to apply "
                    "SDDL (System - ALL, Adm - ALL, LocalService - ALL) to "
                    "the GPIO device object! Status = %#x, DeviceInit = %p\n",
                    Status,
                    DeviceInit);

        goto AddDeviceEnd;
    }

    //
    // Need special-case handling for S0 IRPs. Power framework needs to be
    // notified when D0 IRP is completed. Since WDF does not inherently notify
    // the type of the IRP, install a pre-processing routine to inspect IRP
    // and satisfy power framework requirements.
    //

    Status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
                 DeviceInit,
                 GpioClxWdmPreprocessPowerIrp,
                 IRP_MJ_POWER,
                 NULL,
                 0);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "%s: WdfDeviceInitAssignWdmIrpPreprocessCallback() failed! "
                    "Status = %#x\n",
                    __FUNCTION__,
                    Status);

        goto AddDeviceEnd;
    }

    IrpMinorFunction = IRP_MN_QUERY_DEVICE_RELATIONS;
    Status = WdfDeviceInitAssignWdmIrpPreprocessCallback(
                 DeviceInit,
                 GpioClxWdmPreprocessQueryDeviceRelationsIrp,
                 IRP_MJ_PNP,
                 &IrpMinorFunction,
                 1);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "%s: WdfDeviceInitAssignWdmIrpPreprocessCallback() failed! "
                    "Status = %#x\n",
                    __FUNCTION__,
                    Status);

        goto AddDeviceEnd;
    }

    //
    // Set various attributes for this device. IO on GPIO controllers is
    // typically low-frequency and small-size in nature (setting/clearing a
    // set of GPIO pins). Since the buffers are expected to be small in size,
    // and performance is not a primary concern, use the buffered IO approach
    // to simplify implementation.
    //

    WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoBuffered);

    //
    // Initialize FDO attributes with the GPIO class extension device extension.
    //

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(FdoAttributes, DEVICE_EXTENSION);

    //
    // Set a context cleanup routine to cleanup the device extension.
    //

    FdoAttributes->EvtCleanupCallback = GpioClxEvtDeviceContextCleanup;

    //
    // Set the synchronization scope to device-level. This will cause all the
    // queue callbacks, cancel routine, and DpcForIsr to be serialized at the
    // device level, freeing the class extension from having to deal with those
    // issues.
    //

    FdoAttributes->SynchronizationScope = WdfSynchronizationScopeDevice;

    //
    // Override the context size the framework will create to include the
    // client driver extension, which follows the class extension extension.
    //
    // N.B. WDF requires that an override if specified be at least the
    //      sizeof(DEVICE_EXTENSION).
    //

    MinimumSize = sizeof(DEVICE_EXTENSION);
    ContextSize = FIELD_OFFSET(DEVICE_EXTENSION, ClientExtension);
    ContextSize += Client->CombinedPacket.ControllerContextSize;
    FdoAttributes->ContextSizeOverride = MAX(ContextSize, MinimumSize);
    Status = STATUS_SUCCESS;

AddDeviceEnd:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxProcessAddDevicePostDeviceCreate (
    __in WDFDRIVER Driver,
    __in WDFDEVICE Device
    )

/*++

Routine Description:

    This export routine is called by the GPIO client driver when the driver
    framework invokes the client driver's AddDevice callback. The client driver
    invokes this routine after creating the device object. This routine is
    responsible for supplying the interrupt and DPC callbacks. It is also
    responsible for creating IO queues.

    If the device represents the hub device, then this routine will initialize
    the hub services.

Arguments:

    Driver - Supplies a handle to the driver object created in DriverEntry.

    Device - Supplies a handle to the framework device object.

Return Value:

    NTSTATUS code.

--*/

{

    PGPIO_CLIENT_DRIVER Client;
    GPIO_HUB_REGISTRATION_DATA GpioData;
    PDEVICE_EXTENSION GpioExtension;
    RECORDER_LOG LogHandle;
    NTSTATUS Status;

#if defined(EVENT_TRACING)

    ULONG InstanceId;
    RECORDER_LOG_CREATE_PARAMS RecorderParams;

#endif

    PAGED_CODE();

    Client = GpiopFindClient(Driver);
    if (Client == NULL) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePostDeviceCreate: Failed to find "
                    "GPIO client driver entry in the list!\n");

        Status = STATUS_INVALID_PARAMETER;
        goto ProcessAddDevicePhase1End;
    }

    //
    // Free the buffer that was allocated during pre-create phase as it is
    // no longer required.
    //

    if (Client->TargetName != NULL) {
        GPIO_FREE_POOL(Client->TargetName);
        Client->TargetName = NULL;
    }

#if defined (EVENT_TRACING)

    //
    // Create an "in-flight recorder log"  If there is an issue, the tokens that
    // are recorded in the log will be in the minidump file and can help
    // track the issue.
    //
    // Processing of those tokens back into text depends on having the
    // "traceformat" files, which are generated during the build, and also
    // derivable after the fact from the .pdb file. Dumping of this log depends
    // on a debugger extension, rcdrkd.dll. The trace entries that go into this
    // log can also be consumed through any WPP tracing consumer, like
    // tracelog.exe or traceview.exe.
    //

    RECORDER_LOG_CREATE_PARAMS_INIT(&RecorderParams, NULL);
    RecorderParams.TotalBufferSize = GpioLogBufferSize;
    InstanceId = (ULONG)InterlockedIncrement(&GpioLogHandleInstance);
    RtlStringCbPrintfA(RecorderParams.LogIdentifier,
                       RECORDER_LOG_IDENTIFIER_MAX_CHARS,
                       GPIO_RECORDER_LOG_NAME_FORMAT,
                       InstanceId);

    Status = WppRecorderLogCreate(&RecorderParams, &LogHandle);
    if (!NT_SUCCESS(Status)) {
        LogHandle = GpioLogHandle;
    }

#else

    LogHandle = NULL;

#endif

    //
    // Now that the device object has been created, get the device extension
    // and initialize it.
    //

    Status = GpiopBuildDeviceExtension(Driver,
                                       Device,
                                       Client,
                                       LogHandle);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(LogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePostDeviceCreate: Failed to build "
                    "GPIO device extension! Status = %#x\n",
                    Status);

        goto ProcessAddDevicePhase1End;
    }

    GpioExtension = GpioClxGetDeviceExtension(Device);
    TraceEvents(GpioExtension->LogHandle,
                Info,
                DBG_PNP,
                "GpioClxProcessAddDevicePostDeviceCreate: PDO 0x%p, FDO 0x%p "
                "Extension = 0x%p\n",
                WdfDeviceWdmGetPhysicalDevice(Device),
                WdfDeviceWdmGetDeviceObject(Device),
                GpioExtension);

    //
    // Initialize ACPI eventing on the controller. Since this involves
    // sending bunch of IOCTLs and evaluating AML methods, this is done in
    // a worker thread so that device initialization and enabling of events both
    // happen in parallel.
    //
    // There may not be any pins defined for ACPI eventing on the controller or
    // connecting some of them may fail. Do not treat the failure as fatal as
    // other operations should continue working.
    //

    GpiopManageAcpiEventing(GpioExtension, AcpiEventStateInitialize, FALSE);

    //
    // Setup the default I/O queue for the GPIO controller.
    //

    Status = GpiopSetupPrimaryDeviceIoQueue(GpioExtension);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePostDeviceCreate: Failed to setup"
                    "IO queues! Status = %#x\n",
                    Status);

        goto ProcessAddDevicePhase1End;
    }

    //
    // Setup an I/O target to self. This self-target allows interrupt-related
    // requests to be funneled through WDF I/O queues. This allows interrupt-
    // related requests to be serialized with I/O requests and also
    // synchronized with WDF power engine.
    //
    // Open a handle to the resource hub. This handle is used to query
    // BIOS descriptors associated with a given request.
    //

    Status = GpiopSetupSelfIoTarget(Device, GpioExtension);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePostDeviceCreate: Failed to "
                    "setup self IO target! Status = %#x\n",
                    Status);

        goto ProcessAddDevicePhase1End;
    }

    //
    // Query for the BIOS name of the device from ACPI.
    //

    Status = GpiopQueryDeviceBiosName(Device, GpioExtension);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePostDeviceCreate: Failed to "
                    "query BIOS name for the GPIO from ACPI! "
                    "Status = %#x\n",
                    Status);

        goto ProcessAddDevicePhase1End;
    }

    //
    // Register the client driver with the GPIO hub.
    //
    // N.B. If a request arrives before the device transitions in D0, then
    //      WDF will hold those requests on its queues until such time.
    //

    RtlZeroMemory(&GpioData, sizeof(GPIO_HUB_REGISTRATION_DATA));
    GpioData.TargetName = &GpioExtension->TargetName;
    GpioData.BiosName = &GpioExtension->BiosName;
    GpioData.Context = (PVOID)GpioExtension;
    Status = GpioHubpRegisterGpioDevice(&GpioData);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpioClxProcessAddDevicePostDeviceCreate: Failed to "
                    "register client with the hub! Status = %#x\n",
                    Status);

        goto ProcessAddDevicePhase1End;
    }

ProcessAddDevicePhase1End:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxRegisterClient (
    __in WDFDRIVER Driver,
    __inout PGPIO_CLIENT_REGISTRATION_PACKET RegistrationPacket,
    __in PUNICODE_STRING RegistryPath
    )

/*++

Routine Description:

    This export routine is called by the GPIO client driver to register
    itself with the class extension and supply all its callbacks. The client
    driver invokes this routine when the client driver loads (i.e., from its
    driver entry). This routine is responsible for adding the client driver to
    the global client driver list.

Arguments:

    Driver - Supplies a handle to the client driver's framework driver object.

    RegistrationPacket - Supplies the registration packet that contains
        information about the client driver and all its callbacks.

    RegistryPath - Pointer to the client driver's registry key.

Return Value:

    NTSTATUS code.

--*/

{

    NTSTATUS Status;

    PAGED_CODE();

    if ((Driver == NULL) ||
        (RegistrationPacket == NULL) ||
        (RegistryPath == NULL)) {

        Status = STATUS_INVALID_PARAMETER;
        goto RegisterClientEnd;
    }

    Status = GpiopCreateClient(Driver, RegistrationPacket);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_INIT,
                    "GpioClxRegisterClient: Failed to create client driver "
                    "entry! Status = %#x\n",
                    Status);
    }

RegisterClientEnd:
    return Status;
}

__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpioClxUnregisterClient (
    __in WDFDRIVER Driver
    )

/*++

Routine Description:

    This export routine is called by the GPIO client driver to unregister itself
    with the class extension. The client driver invokes this routine when the
    client driver unloads (i.e., from its driver unload entry point). This
    routine is removing the client driver from the list of registered clients.

Arguments:

    Driver - Supplies a handle to the client driver's framework driver object.

Return Value:

    NTSTATUS code.

--*/

{

    NTSTATUS Status;

    PAGED_CODE();

    Status = GpiopDeleteClient(Driver);
    return Status;
}


__drv_raisesIRQL(GPIO_HUB_SYNCHRONIZATION_IRQL)
VOID
GpioClxAcquireInterruptLock (
    __in PVOID Context,
    __in BANK_ID BankId
    )

/*++

Routine Description:

    This export routine is called by the GPIO client driver to acquire the
    interrupt lock in preparation to perform actions that require
    synchronization with the interrupt service routine.

    If the controller can handle interrupts at DIRQL level, then this routine
    will acquire the interrupt lock. Otherwise, it will acquire the
    controller-wide lock.

    N.B. This routine will be a no-op if the client driver tries to acquire a
         lock that the class extension had already acquired prior to calling
         into the client driver.

Arguments:

    Context - Supplies a pointer to the client driver's device extension.

    BankId - Supplies the ID for the bank for which the interrupt lock
        needs to be acquired.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;

    //
    // If the client driver supplied an invalid parameter then bugcheck.
    // Masking such errors would likely lead to nasty failures due to incorrect
    // synchronization. Make synchronization issues obvious to the client
    // driver.
    //

    if (Context == NULL) {
        GpioExtension = NULL;

    } else {
        GpioExtension = CONTAINING_RECORD(Context,
                                          DEVICE_EXTENSION,
                                          ClientExtension);
    }

    if ((GpioExtension == NULL) ||
        (GpioExtension->Signature != GPIO_DEVICE_EXTENSION_SIGNATURE)) {

        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "%s: Client driver supplied an invalid context! "
                    "Extension = %p, Bank ID = 0x%x\n",
                    __FUNCTION__,
                    Context,
                    BankId);

        KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                     INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                     (ULONG_PTR)Context,
                     0x1,
                     0);
    }

    //
    // Get the entry for the supplied bank id.
    //

    GpioBank = GpiopGetBankEntry(GpioExtension, BankId);
    if (GpioBank == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "%s: Client driver specified an invalid bank ID! "
                    "Bank ID = 0x%x, Total banks = %#x\n",
                    __FUNCTION__,
                    BankId,
                    GpioExtension->TotalBanks);

        KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                     INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                     (ULONG_PTR)GpioExtension,
                     0x2,
                     (GpioExtension->TotalBanks << 16) | BankId);
    }

    //
    // If interrupts are being serviced at passive-level, then the
    // per-controller lock is used for synchronization. Otherwise,
    // the synchronization is done using the interrupt lock (at DIRQL level).
    //
    // N.B. It is a fatal error for the client driver to acquire a lock that
    //      has already been acquired by the class extension already.
    //

    if (GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) == FALSE) {
        if (GpioBank->InterruptLockOwner == KeGetCurrentThread()) {

            TraceEvents(GpioExtension->LogHandle,
                        Error,
                        DBG_INTERRUPT,
                        "%s: Client driver tried to acquire the interrupt "
                        "lock that was already acquired by current thread! "
                        "Extn = %p\n",
                        __FUNCTION__,
                        GpioExtension);

            KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                         INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                         (ULONG_PTR)GpioExtension,
                         0x3,
                         0);
        }

        GPIO_ACQUIRE_BANK_INTERRUPT_LOCK(GpioBank);

        GpioBank->InterruptLockClientOwned = TRUE;

    } else {
        if (GpioBank->PassiveLockOwner == KeGetCurrentThread()) {

            TraceEvents(GpioExtension->LogHandle,
                        Error,
                        DBG_INTERRUPT,
                        "%s: Client driver tried to acquire the passive-level "
                        "lock that was already acquired by current thread! "
                        "Extn = %p\n",
                        __FUNCTION__,
                        GpioExtension);

            KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                         INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                         (ULONG_PTR)GpioExtension,
                         0x3,
                         0);
        }

        GPIO_ACQUIRE_BANK_LOCK(GpioBank);

        GpioBank->PassiveLockClientOwned = TRUE;
    }

    return;
}

__drv_minIRQL(DISPATCH_LEVEL + 1)
VOID
GpioClxReleaseInterruptLock (
    __in PVOID Context,
    __in BANK_ID BankId
    )

/*++

Routine Description:

    This export routine is called by the GPIO client driver to release a
    previously acquired interrupt lock.

    If the controller can handle interrupts at DIRQL level, then this routine
    will release the interrupt lock. Otherwise, it will release the
    controller-wide lock.

    N.B. This routine will be a no-op if the client driver tries to release a
         lock that it never acquired.

Arguments:

    Context - Supplies a pointer to the client driver's device extension.

    BankId - Supplies the ID for the bank for which the interrupt lock
        needs to be acquired.

Return Value:

    None.

--*/

{

    PGPIO_BANK_ENTRY GpioBank;
    PDEVICE_EXTENSION GpioExtension;

    //
    // If the client driver supplied an invalid parameter then bugcheck.
    // Masking such errors would likely lead to nasty failures due to incorrect
    // synchronization. Make synchronization issues obvious to the client
    // driver.
    //

    if (Context == NULL) {
        GpioExtension = NULL;

    } else {
        GpioExtension = CONTAINING_RECORD(Context,
                                          DEVICE_EXTENSION,
                                          ClientExtension);
    }

    if ((GpioExtension == NULL) ||
        (GpioExtension->Signature != GPIO_DEVICE_EXTENSION_SIGNATURE)) {

        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "%s: Client driver supplied an invalid context! "
                    "Extension = %p, Bank ID = 0x%x\n",
                    __FUNCTION__,
                    Context,
                    BankId);

        KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                     INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                     (ULONG_PTR)Context,
                     0x1,
                     0);
    }

    //
    // Get the entry for the supplied bank id.
    //

    GpioBank = GpiopGetBankEntry(GpioExtension, BankId);
    if (GpioBank == NULL) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_INTERRUPT,
                    "%s: Client driver specified an invalid bank ID! "
                    "Bank ID = 0x%x, Total banks = %#x\n",
                    __FUNCTION__,
                    BankId,
                    GpioExtension->TotalBanks);

        KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                     INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                     (ULONG_PTR)GpioExtension,
                     0x2,
                     (GpioExtension->TotalBanks << 16) | BankId);
    }

    //
    // If interrupts are being serviced at passive-level, then the
    // per-bank passive lock is used for synchronization. Otherwise,
    // the synchronization is done using the interrupt lock (at DIRQL level).
    //
    // N.B. It is a fatal error for the client driver to release a lock that
    //      was not acquired by it.
    //

    if (GPIO_IS_PASSIVE_IRQL_ONLY_DEVICE(GpioExtension) == FALSE) {
        if (GpioBank->InterruptLockClientOwned == FALSE) {
            TraceEvents(GpioExtension->LogHandle,
                        Error,
                        DBG_INTERRUPT,
                        "%s: Client driver tried to release the interrupt "
                        "lock that it did not own! Extn = %p\n",
                        __FUNCTION__,
                        GpioExtension);

            KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                         INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                         (ULONG_PTR)GpioExtension,
                         0x4,
                         0x0);
        }

        GpioBank->InterruptLockClientOwned = FALSE;
        GPIO_RELEASE_BANK_INTERRUPT_LOCK(GpioBank);

    } else {
        if (GpioBank->PassiveLockClientOwned == FALSE) {
            TraceEvents(GpioExtension->LogHandle,
                        Error,
                        DBG_INTERRUPT,
                        "%s: Client driver tried to release the passive-level "
                        "lock that it did not own! Bank ID = 0x%x, "
                        "Total banks = %#x.\n",
                        __FUNCTION__,
                        BankId,
                        GpioExtension->TotalBanks);

            KeBugCheckEx(GPIO_CONTROLLER_DRIVER_ERROR,
                         INTERRUPT_ACQUIRE_LOCK_UNLOCK_FAILURE,
                         (ULONG_PTR)GpioExtension,
                         0x4,
                         0x0);
        }

        GpioBank->PassiveLockClientOwned = FALSE;

        GPIO_RELEASE_BANK_LOCK(GpioBank);
    }

    return;
}


//
// ----------------------------------------------------- Internal only routines
//

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopAssignTargetName (
    __in PWDFDEVICE_INIT DeviceInit,
    __deref_out_ecount_full(GPIO_DEVICE_NAME_ECOUNT + 2) __nullterminated
        PWCHAR *TargetNameOut
    )

/*++

Routine Description:

    This routine assigns a unique name to the GPIO controller instance.

Arguments:

    DeviceInit - Supplies a pointer to a framework-allocated WDFDEVICE_INIT
        structure.

    TargetNameOut - Supplies a pointer to a variable that receives the buffer
        allocated for the name.

Return Value:

    NTSTATUS code.

--*/

{

    USHORT Length;
    UNICODE_STRING NameBuffer;
    NTSTATUS Status;
    PWCHAR TargetName;
    ULONG Value;

    PAGED_CODE();

    //
    // Allocate string for the prefix (includes NULL) and upto 10 digits.
    //

    Length = (USHORT)(wcslen(GPIO_DEVICE_NAME_FORMAT) + 10) * sizeof(WCHAR);
    TargetName = GPIO_ALLOCATE_POOL(NonPagedPoolNx, Length);
    if (TargetName == NULL) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpiopAssignTargetName: Failed to allocate memory for "
                    "target name! Length = %d\n",
                    Length);

        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto AssignTargetNameEnd;
    }

    RtlZeroMemory(TargetName, Length);
    RtlInitEmptyUnicodeString(&NameBuffer, TargetName, Length);
    Value = InterlockedIncrement((PLONG)&GpioInstanceNumber);
    Status = RtlUnicodeStringPrintf(&NameBuffer,
                                    GPIO_DEVICE_NAME_FORMAT,
                                    Value);

    if (NT_SUCCESS(Status)) {
        Status = WdfDeviceInitAssignName(DeviceInit, &NameBuffer);
    }

    if (NT_SUCCESS(Status)) {
        *TargetNameOut = TargetName;
    }

AssignTargetNameEnd:
    if (!NT_SUCCESS(Status) && (TargetName != NULL)) {
        GPIO_FREE_POOL(TargetName);
    }

    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopQueryDeviceBiosName (
    __in WDFDEVICE Device,
    __in PDEVICE_EXTENSION GpioExtension
)

/*++

Routine Description:

    This routine queues the BIOS name for the device from ACPI.

Arguments:

    Device - Supplies a handle to the framework device object.

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

Return Value:

    NTSTATUS code.

--*/

{

#define INITIAL_BUFFER_SIZE (64 * sizeof(WCHAR))

    PWCHAR BiosNameBuffer;
    ULONG_PTR BiosNameLength;
    WDFIOTARGET IoTarget;
    USHORT Length;
    WDF_MEMORY_DESCRIPTOR OutputMemoryDescriptor;
    NTSTATUS Status;

    PAGED_CODE();

    //
    // Query the BIOS name for the device from ACPI. The GPIO controller should
    // be defined in the ACPI namespace and thus ACPI should be loaded as
    // either the bus driver or a filter on the device stack.
    //
    // N.B. The BIOS name could be used by the GPIO hub at DIRQL level and thus
    //      needs to be allocated from non-paged pool.
    //

    BiosNameBuffer = NULL;
    IoTarget = WdfDeviceGetIoTarget(Device);
    Length = INITIAL_BUFFER_SIZE;
    do {
        BiosNameBuffer = GPIO_ALLOCATE_POOL(NonPagedPoolNx, Length);
        if (BiosNameBuffer == NULL) {
            Status = STATUS_INSUFFICIENT_RESOURCES;
            goto SetupResourceHubEnd;
        }

        WDF_MEMORY_DESCRIPTOR_INIT_BUFFER(&OutputMemoryDescriptor,
                                          BiosNameBuffer,
                                          Length);

        //
        // Send the request to query the BIOS name of the device. If the
        // output buffer is insufficient, then this request is expected to
        // fail with STATUS_BUFFER_TOO_SMALL.
        //

        Status = WdfIoTargetSendIoctlSynchronously(
                     IoTarget,
                     NULL,
                     IOCTL_ACPI_QUERY_DEVICE_BIOS_NAME,
                     NULL,
                     &OutputMemoryDescriptor,
                     NULL,
                     &BiosNameLength);


        //
        // If the output buffer is too small, then retry with a larger buffer
        // size.
        //

        if (Status == STATUS_BUFFER_TOO_SMALL) {
            if (Length <= UNICODE_STRING_MAX_BYTES / 2) {
                Length = Length * 2;
                GPIO_FREE_POOL(BiosNameBuffer);
            } else {
                Status = STATUS_INVALID_BUFFER_SIZE;
            }
        }

    } while (Status == STATUS_BUFFER_TOO_SMALL);

    if (!NT_SUCCESS(Status)) {
        goto SetupResourceHubEnd;
    }

    //
    // Register the BIOS name for the device in the device extension. The
    // name length obtained from ACPI is inclusive of the terminating NULL
    // character.
    //

    GpioExtension->BiosName.MaximumLength = Length;
    NT_ASSERT(BiosNameLength <= Length);
    GpioExtension->BiosName.Length = (USHORT)BiosNameLength - sizeof(WCHAR);
    GpioExtension->BiosName.Buffer = BiosNameBuffer;

SetupResourceHubEnd:
    if (!NT_SUCCESS(Status) && (BiosNameBuffer != NULL)) {
        GPIO_FREE_POOL(BiosNameBuffer);
    }

    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopSetupPrimaryDeviceIoQueue (
    __in PDEVICE_EXTENSION GpioExtension
    )

/*++

Routine Description:

    This routine creates all the queues required to perform I/O on the
    controller.

    N.B. Currently this only sets up a single sequential (default) IO queue. In
         future the read/write requests can be put on one queue and the default
         queue could be used for everything else.

Arguments:

    Device - Supplies a handle to the framework device object.

Return Value:

    NTSTATUS code.

--*/

{

    WDF_OBJECT_ATTRIBUTES IoQueueAttributes;
    WDFQUEUE IoQueue;
    WDF_IO_QUEUE_CONFIG QueueConfiguration;
    NTSTATUS Status;

    PAGED_CODE();

    //
    // Create the primary device-level queue. Mark it as a parallel queue
    // such that framework forwards the requests as they arrive.
    //

    WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&QueueConfiguration,
                                           WdfIoQueueDispatchParallel);

    QueueConfiguration.EvtIoRead = GpioClxEvtProcessReadRequest;
    QueueConfiguration.EvtIoWrite = GpioClxEvtProcessWriteRequest;
    QueueConfiguration.EvtIoDeviceControl = GpioClxEvtProcessDeviceIoControl;
    QueueConfiguration.EvtIoInternalDeviceControl = GpioClxEvtProcessInternalDeviceIoControl;

    //
    // Initialize the attributes of the queue. Set the execution level to
    // passive to force all IRPs to be sent to the driver at passive IRQL.
    //

    WDF_OBJECT_ATTRIBUTES_INIT(&IoQueueAttributes);
    IoQueueAttributes.SynchronizationScope = WdfSynchronizationScopeQueue;
    IoQueueAttributes.ExecutionLevel = WdfExecutionLevelPassive;

    //
    // Create the IO queue.
    //

    Status = WdfIoQueueCreate(GpioExtension->Device,
                              &QueueConfiguration,
                              &IoQueueAttributes,
                              &IoQueue);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpiopSetupPrimaryDeviceIoQueue: Failed to create WDF IO "
                    "queue! Status = %#x\n",
                    Status);

        goto SetupPrimaryDeviceIoQueueEnd;
    }

SetupPrimaryDeviceIoQueueEnd:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopSetupSelfIoTarget (
    __in WDFDEVICE Device,
    __in PDEVICE_EXTENSION GpioExtension
    )

/*++

Routine Description:

    This routine sets up an IO target to self. The self-target allows
    interrupt-related requests to be funneled through WDF I/O queues. This
    allows interrupt-related requests to be serialized with I/O requests and
    also synchronized with WDF power engine.

Arguments:

    Device - Supplies a handle to the framework device object.

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

Return Value:

    NTSTATUS code.

--*/

{

    WDF_OBJECT_ATTRIBUTES Attributes;
    WDF_IO_TARGET_OPEN_PARAMS OpenParameters;
    NTSTATUS Status;
    PDEVICE_OBJECT WdmDevice;

    PAGED_CODE();

    WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
    Attributes.ParentObject = Device;
    Status = WdfIoTargetCreate(Device,
                               &Attributes,
                               &GpioExtension->SelfIoTarget);

    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpiopSetupSelfIoTarget: Failed to create self IO  target! "
                    "Status = %#x\n",
                    Status);

        goto SetupSelfIoTargetEnd;
    }

    WdmDevice = WdfDeviceWdmGetDeviceObject(Device);
    WDF_IO_TARGET_OPEN_PARAMS_INIT_EXISTING_DEVICE(&OpenParameters, WdmDevice);

    //
    // Open an IO target to ourselves.
    //

    Status = WdfIoTargetOpen(GpioExtension->SelfIoTarget, &OpenParameters);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioExtension->LogHandle,
                    Error,
                    DBG_PNP,
                    "GpiopSetupSelfIoTarget: Failed to open self IO  target! "
                    "Status = %#x\n",
                    Status);
    }

SetupSelfIoTargetEnd:
    return Status;
}

_Must_inspect_result_
__drv_requiresIRQL(PASSIVE_LEVEL)
NTSTATUS
GpiopOpenResourceHubTarget (
    __in WDFDEVICE Device,
    __out WDFIOTARGET *HubTarget
    )

/*++

Routine Description:

    This routine sets up an IO target to the Resource Hub and registers the
    GPIO device with it supplying the BIOS name <--> NT device name mapping.
    This is required for the Resource hub to be able to forward requests to
    this device.

Arguments:

    Device - Supplies a handle to the framework device object.

    HubTarget - Supplies a pointer to a variable that receives the resource
        hub target.

Return Value:

    NTSTATUS code.

--*/

{

    WDF_OBJECT_ATTRIBUTES Attributes;
    WDFIOTARGET IoTarget;
    WDF_IO_TARGET_OPEN_PARAMS OpenParameters;
    UNICODE_STRING ResourceHubName;
    NTSTATUS Status;

    PAGED_CODE();

    //
    // Create an IO target to the Resource hub.
    //

    IoTarget = NULL;
    WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
    Attributes.ParentObject = Device;
    Status = WdfIoTargetCreate(Device, &Attributes, &IoTarget);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpiopOpenResourceHubTarget: Failed to create resource "
                    "hub target! Status = %#x\n",
                    Status);

        goto OpenResourceHubTargetEnd;
    }

    RtlInitUnicodeString(&ResourceHubName, RESOURCE_HUB_DEVICE_NAME);
    WDF_IO_TARGET_OPEN_PARAMS_INIT_OPEN_BY_NAME(&OpenParameters,
                                                &ResourceHubName,
                                                STANDARD_RIGHTS_ALL);

    //
    // Open the Resource hub IO target.
    //

    Status = WdfIoTargetOpen(IoTarget, &OpenParameters);
    if (!NT_SUCCESS(Status)) {
        TraceEvents(GpioLogHandle,
                    Error,
                    DBG_PNP,
                    "GpiopOpenResourceHubTarget: Failed to open resource hub "
                    "target! Status = %#x\n",
                    Status);

        goto OpenResourceHubTargetEnd;
    }

    *HubTarget = IoTarget;

    //
    // N.B. The IO target does not need to be closed as it would have been set
    //      within the device extension if it was opened successfully.
    //

OpenResourceHubTargetEnd:
    if (!NT_SUCCESS(Status) && (IoTarget != NULL)) {
        WdfObjectDelete(IoTarget);
    }

    return Status;
}

