/*++

Copyright (c) 1997  Microsoft Corporation

Module Name:

    thermal.c

Abstract:

    Thermal Zone support

    A small discourse on the use and function of the THRM_WAIT_FOR_NOTIFY
    flag. This flag was added to ensure that at least one Notify() operation
    occured between each query of the temperature. In other words, we didn't
    want to loop forever asking and receiving the same temperature information.

    One of the side effects of this flag is that if we get a QUERY, then
    a SET (instead of another QUERY), then the set must clear the flag.
    Failure to do so will prevent the ThermalLoop() code from ever completing
    the IRP. And that means that the temperature mechanisms will stop working.

Author:

    Stephane Plante (splante)

Environment:

    NT Kernel Model Driver only

Revision History:

    July 7, 1997    - Complete Rewrite
--*/

#include "pch.h"

WMIGUIDREGINFO ACPIThermalGuidList =
{
    &THERMAL_ZONE_GUID,
    1,
    0
};

//
// Spinlock to protect the thermal list
//
KSPIN_LOCK  AcpiThermalLock;

//
// List entry to store the thermal requests on
//
LIST_ENTRY  AcpiThermalList;

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, ACPIThermalStartDevice)
#pragma alloc_text(PAGE, ACPIThermalWorker)
#pragma alloc_text(PAGE, ACPIThermalQueryWmiRegInfo)
#pragma alloc_text(PAGE, ACPIThermalQueryWmiDataBlock)
#pragma alloc_text(PAGE, ACPIThermalWmi)
#endif

VOID
ACPIThermalCalculateProcessorMask(
    IN PNSOBJ           ProcessorObject,
    IN PTHRM_INFO       Thrm
    )
/*++

Routine Description:

    This routine, which is only called from ACPIThermalWorker, has been
    created so that we don't have to worry about locking down the
    ACPIThermalWorker, takes a processor object from the namespace and
    sets the proper affinity bit in the thermal info

Arguments:

    ProcessorObject - Pointer to Namespace Processor Object
    Thrm            - Thermal Information Structure

Return Value:

    None

--*/
{
    KIRQL               OldIrql;
    PDEVICE_EXTENSION   ProcessorExtension;

    //
    // Sanity check
    //
    if (ProcessorObject == NULL) {

        return;

    }

    //
    // We need the spinlock to deref the processor extension
    //
    KeAcquireSpinLock( &AcpiDeviceTreeLock, &OldIrql );

    //
    // The context pointer is the device extension
    //
    ProcessorExtension = (PDEVICE_EXTENSION) ProcessorObject->Context;
    if (ProcessorExtension) {

        //
        // We know what index it is within the processor list.
        // This should be a good enough guess for now
        //
        Thrm->Info.Processors |= (1 << ProcessorExtension->Processor.ProcessorIndex);

    }

    //
    // Done with the spinlock
    //
    KeReleaseSpinLock( &AcpiDeviceTreeLock, OldIrql );

}

VOID
ACPIThermalCancelRequest (
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp
    )
/*++

Routine Description:

    This routine cancels an outstanding thermal request

Arguments

    DeviceObject    - The device which has a request being cancelled
    Irp             - The cancelling irp

Return Value:

    None

--*/
{
    KIRQL               oldIrql;
    PDEVICE_EXTENSION   deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);

#if DBG
    ULONGLONG           currentTime = KeQueryInterruptTime();

    ACPIThermalPrint( (
        ACPI_PRINT_THERMAL,
        deviceExtension,
        currentTime,
        "ACPIThermalCancelRequest: Irp %08lx\n",
        Irp
        ) );
#endif

    //
    // We no longer need the cancel lock
    //
    IoReleaseCancelSpinLock (Irp->CancelIrql);

    //
    // We do however need the thermal queue lock
    //
    KeAcquireSpinLock( &AcpiThermalLock, &oldIrql );

    //
    // Remove the irp from the list that it is on
    //
    RemoveEntryList( &(Irp->Tail.Overlay.ListEntry) );

    //
    // Done with the thermal lock now
    //
    KeReleaseSpinLock( &AcpiThermalLock, oldIrql );

    //
    // Complete the irp now
    //
    Irp->IoStatus.Status = STATUS_CANCELLED;
    IoCompleteRequest (Irp, IO_NO_INCREMENT);
}

VOID
EXPORT
ACPIThermalComplete (
    IN PNSOBJ               AcpiObject,
    IN NTSTATUS             Status,
    IN POBJDATA             Result  OPTIONAL,
    IN PVOID                DeviceExtension
    )
/*++

Routine Description:

    This routine is called when the interpreter has completed a request

Arguments:

    AcpiObject  - The request that was completed
    Status      - The status of the request
    Result      - What the result of the request was
    DevExt      - The context of the request

Return Value:

    NONE

--*/
{
    ACPIThermalLoop (DeviceExtension, THRM_BUSY);
}

BOOLEAN
ACPIThermalCompletePendingIrps(
    IN  PDEVICE_EXTENSION   DeviceExtension,
    IN  PTHRM_INFO          Thermal
    )
/*++

Routine Description:

    This routine is called, with no spinlock being held. This routine
    completes any IOCTLs associated with the device object

    This routine will return TRUE if it completed any requests, false
    otherwise

Arguments:

    DeviceExtension - The device extension whose requests we want to complete
    Thermal         - Pointer to the thermal information for the extension

Return Value:

    BOOLEAN
--*/
{
    BOOLEAN                 handledRequest  = FALSE;
    KIRQL                   oldIrql;
    LIST_ENTRY              doneList;
    PDEVICE_EXTENSION       irpExtension;
    PDEVICE_OBJECT          deviceObject;
    PIO_STACK_LOCATION      irpSp;
    PIRP                    irp;
    PLIST_ENTRY             listEntry;
    PTHERMAL_INFORMATION    thermalInfo;

    //
    // Initialize the list that will hold the requets that we need to complete
    //
    InitializeListHead( &doneList );
    //
    // Acquire the thermal lock so that we can pend these requests
    //
    KeAcquireSpinLock( &AcpiThermalLock, &oldIrql );

    //
    // Walk the list of pending irps to see which ones match this extensions
    //
    listEntry = AcpiThermalList.Flink;
    while (listEntry != &AcpiThermalList) {

        //
        // Grab the irp from the list entry and update the next list entry
        // that we will look at
        //
        irp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry );
        listEntry = listEntry->Flink;

        //
        // We need the current irp stack location
        //
        irpSp = IoGetCurrentIrpStackLocation( irp );

        //
        // Grab the device object from the irp stack and turn that into a
        // device extension
        //
        irpExtension = ACPIInternalGetDeviceExtension( irpSp->DeviceObject );

        //
        // Is this an irp that we care about? IE: does the target match the
        // one specified in this function
        //
        if (irpExtension != DeviceExtension) {

            continue;

        }

        //
        // If this is a query information irp then, we must be able to set the
        // cancel routine to NULL to make sure that it cannot be cancelled on
        // us
        //
        if (irpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_THERMAL_QUERY_INFORMATION) {

            if (IoSetCancelRoutine(irp, NULL) == NULL) {

                //
                // Cancel routine is active, stop processing this irp and move on
                //
                continue;

            }

            //
            // Copy the data that we got back to the irp
            //
            DeviceExtension->Thermal.Flags |= THRM_WAIT_FOR_NOTIFY;
            thermalInfo = (PTHERMAL_INFORMATION) irp->AssociatedIrp.SystemBuffer;
            memcpy (thermalInfo, Thermal, sizeof (THERMAL_INFORMATION));

            //
            // Set the parameters that we will return
            //
            irp->IoStatus.Information   = sizeof(THERMAL_INFORMATION);

        } else {

            //
            // Set the parameters that we will return
            //
            irp->IoStatus.Information = 0;

        }

        //
        // Always succeed these irps
        //
        irp->IoStatus.Status        = STATUS_SUCCESS;

        //
        // Remove the entry from the list
        //
        RemoveEntryList( &(irp->Tail.Overlay.ListEntry) );

        //
        // Insert the list into the next queue so that we know to complete it
        // later on
        //
        InsertTailList( &doneList, &(irp->Tail.Overlay.ListEntry) );

    }

    //
    // At this point, drop our thermal lock
    //
    KeReleaseSpinLock( &AcpiThermalLock, oldIrql );

    //
    // Walk the list of irpts to be completed
    //
    listEntry = doneList.Flink;
    while (listEntry != &doneList) {

        //
        // Grab the irp from the list entry and update the next list entry
        // that we will look at
        //
        irp = CONTAINING_RECORD( listEntry, IRP, Tail.Overlay.ListEntry );
        listEntry = listEntry->Flink;
        RemoveEntryList( &(irp->Tail.Overlay.ListEntry) );

        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            DeviceExtension,
            KeQueryInterruptTime(),
            "Completing Irp 0x%x\n",
            irp
            ) );

        //
        // Now complete the irp
        //
        IoCompleteRequest( irp, IO_NO_INCREMENT );

        //
        // Remember that we handled a request
        //
        handledRequest = TRUE;

    }

    //
    // Return wether or not we handled a request
    return handledRequest;
}

NTSTATUS
ACPIThermalDeviceControl (
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )
/*++

Routine Description:

    Fixed button device IOCTL handler

Arguments:

    DeviceObject    - fixed feature button device object
    Irp             - the ioctl request

Return Value:

    NTSTATUS

--*/
{
    KIRQL                       oldIrql;
    PIO_STACK_LOCATION          IrpSp           = IoGetCurrentIrpStackLocation(Irp);
    PDEVICE_EXTENSION           deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);
    PTHERMAL_INFORMATION        thermalInfo;
    PULONG                      Mode;
    PTHRM_INFO                  Thrm            = deviceExtension->Thermal.Info;
    NTSTATUS                    Status          = STATUS_PENDING;
    ULONG                       ThermalWork     = 0;
    ULONGLONG                   currentTime;

    //
    // Do not allow user mode IRPs in this routine
    //
    if (Irp->RequestorMode != KernelMode) {

        return ACPIDispatchIrpInvalid( DeviceObject, Irp );

    }

#if DBG
    currentTime = KeQueryInterruptTime();
#endif

    switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {
    case IOCTL_THERMAL_QUERY_INFORMATION:

        //
        // If this irp's stamp does not match the known last stamp, then we
        // need a new temp now
        //
        thermalInfo = (PTHERMAL_INFORMATION) Irp->AssociatedIrp.SystemBuffer;
        if (thermalInfo->ThermalStamp != Thrm->Info.ThermalStamp) {

            ThermalWork = THRM_TEMP | THRM_WAIT_FOR_NOTIFY;

        }
#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            deviceExtension,
            currentTime,
            "%08x - THERMAL_QUERY_INFORMATION: %x - %x\n",
            Irp,
            thermalInfo->ThermalStamp,
            Thrm->Info.ThermalStamp
            ) );
#endif

        break;

    case IOCTL_THERMAL_SET_COOLING_POLICY:

        //
        // Set the thermal zone's policy mode
        //
        Thrm->Mode = *((PUCHAR) Irp->AssociatedIrp.SystemBuffer);
        ThermalWork = THRM_MODE | THRM_TRIP_POINTS | THRM_WAIT_FOR_NOTIFY;

#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            deviceExtension,
            currentTime,
            "%08x - SET_COOLING_POLICY: %x\n",
            Irp,
            Thrm->Mode
            ) );
#endif

        break;

    case IOCTL_RUN_ACTIVE_COOLING_METHOD:

        Thrm->CoolingLevel = *((PUCHAR) Irp->AssociatedIrp.SystemBuffer);
        ThermalWork = THRM_COOLING_LEVEL | THRM_WAIT_FOR_NOTIFY;

#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            deviceExtension,
            currentTime,
            "%08x - ACTIVE_COOLING_LEVEL: %x\n",
            Irp,
            Thrm->CoolingLevel
            ) );
#endif

        break;

    default:

        Irp->IoStatus.Information = 0;
        Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
        IoCompleteRequest( Irp, IO_NO_INCREMENT );
        return STATUS_NOT_SUPPORTED;

    }

    //
    // Grab the thermal lock, queue the request to the proper place, and make
    // sure to set a cancel routine --- no that we will only allow a cancel
    // routine if this is a query irp
    //
    KeAcquireSpinLock( &AcpiThermalLock, &oldIrql );

    //
    // There is one fly in the ointment: What to do if the device is no longer
    // there. The only way to really handle that is to just fail the request.
    // Its important to note that this check is done while the ThermalLock
    // is being held because the code that builds a SurpriseRemoved extension
    // will attempt to call AcpiThermalCompletePendingIrps which also
    // acquires this lock.
    //
    if (deviceExtension->Flags & DEV_TYPE_SURPRISE_REMOVED) {

        KeReleaseSpinLock( &AcpiThermalLock, oldIrql );
        Irp->IoStatus.Status = STATUS_NO_SUCH_DEVICE;
        Irp->IoStatus.Information = 0;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_NO_SUCH_DEVICE;

    }

    if (IrpSp->Parameters.DeviceIoControl.IoControlCode == IOCTL_THERMAL_QUERY_INFORMATION) {

        IoSetCancelRoutine (Irp, ACPIThermalCancelRequest);
        if (Irp->Cancel && IoSetCancelRoutine( Irp, NULL ) ) {

            //
            // If we got here, that means that the irp has been cancelled and we
            // beat the IO manager to the ThermalLock. So release the irp, and
            // mark the irp as being cancelled
            //
            KeReleaseSpinLock( &AcpiThermalLock, oldIrql );
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = STATUS_CANCELLED;
            IoCompleteRequest( Irp, IO_NO_INCREMENT );
            return STATUS_CANCELLED;

        }

        //
        // If we got to this point, we are going to queue the request and do some
        // work on it later
        //
        IoMarkIrpPending( Irp );

    }

    //
    // If we got here, we know we can queue the irp in the outstanding
    // work list entry
    //
    InsertTailList( &AcpiThermalList, &(Irp->Tail.Overlay.ListEntry) );

    //
    // Done with the lock at this point
    //
    KeReleaseSpinLock( &AcpiThermalLock, oldIrql );

    //
    // Fire off the workter thread
    //
    ACPIThermalLoop (deviceExtension, ThermalWork);
    return Status;
}

VOID
ACPIThermalEvent (
    IN PDEVICE_OBJECT   DeviceObject,
    IN ULONG            EventData
    )
/*++

Routine Description:

    This routine handles thermal events

Arguments:

    DeviceObject    - The device that received the event
    EventData       - The event that just happened

Return Value:

    NONE

--*/
{
    PDEVICE_EXTENSION   deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);
    ULONG               clear;

    ACPIThermalPrint( (
        ACPI_PRINT_THERMAL,
        deviceExtension,
        KeQueryInterruptTime(),
        "ACPIThermalEvent - Notify(%x)\n",
        EventData
        ) );

    //
    // Handle event type
    //
    clear = 0;
    switch (EventData) {
    case 0x80:

        //
        // Tempature changed notification
        //
        clear = THRM_WAIT_FOR_NOTIFY | THRM_TEMP;
        break;

    case 0x81:

        //
        // Trips points changed notification
        //
        clear = THRM_WAIT_FOR_NOTIFY | THRM_TEMP | THRM_TRIP_POINTS;
        break;

    default:
        break;
    }

    ACPIThermalLoop (deviceExtension, clear);
}

NTSTATUS
ACPIThermalFanStartDevice(
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )
/*++

Routine Description:

    This is the routine that processes the start device for the fans

Arguments:

    DeviceObject    - The fan device
    Irp             - The start request

Return Value:

    NTSTATUS

--*/
{
    PDEVICE_EXTENSION   deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);
    PIO_STACK_LOCATION  irpStack        = IoGetCurrentIrpStackLocation( Irp );
    UCHAR               minorFunction   = irpStack->MinorFunction;

    //
    // There is nothing to do when starting a fan --- it is really being
    // controlled by the thermal zones
    //
    deviceExtension->DeviceState = Started;

    //
    // Complete the request
    //
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = (ULONG_PTR) NULL;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );

    //
    // Let the world know
    //
    ACPIDevPrint( (
        ACPI_PRINT_THERMAL,
        deviceExtension,
        "(0x%08lx): %s = 0x%08lx\n",
        Irp,
        ACPIDebugGetIrpText(IRP_MJ_PNP, minorFunction),
        STATUS_SUCCESS
        ) );

    //
    // Done
    //
    return STATUS_SUCCESS;
}

VOID
ACPIThermalLoop (
    IN PDEVICE_EXTENSION    DeviceExtension,
    IN ULONG                Clear
    )
/*++

Routine Description:

    This is the routine that processes all thermal events

Arguments:

    DevExt  - The device extension of the thermal zone
    Clear   - Bits to clear

Return Value:

    NTSTATUS

--*/
{
    BOOLEAN     doneRequests;
    BOOLEAN     lockHeld;
    KIRQL       oldIrql;
    PTHRM_INFO  thermal;
    NTSTATUS    status;

    thermal = DeviceExtension->Thermal.Info;

    KeAcquireSpinLock (&DeviceExtension->Thermal.SpinLock, &oldIrql);
    lockHeld = TRUE;

    DeviceExtension->Thermal.Flags &= ~Clear;

    //
    // If we're not in the service loop, enter it now
    //
    if (!(DeviceExtension->Thermal.Flags & THRM_IN_SERVICE_LOOP)) {
        DeviceExtension->Thermal.Flags |= THRM_IN_SERVICE_LOOP;

        //
        // Loop while there's work to be done
        //
        for (; ;) {

            //
            // Synchronize the thermal zone
            //
            if (!lockHeld) {

                KeAcquireSpinLock(&DeviceExtension->Thermal.SpinLock, &oldIrql);
                lockHeld = TRUE;

            }

            //
            // If some work is pending, wait for it to complete
            //
            if (DeviceExtension->Thermal.Flags & THRM_BUSY) {

                break;

            }

            //
            // Make sure that the thermal zone is initialized. This must
            // be the first thing that we do in the loop!!!
            //
            if (!(DeviceExtension->Thermal.Flags & THRM_INITIALIZE) ) {

                DeviceExtension->Thermal.Flags |= THRM_BUSY | THRM_INITIALIZE;
                ACPISetDeviceWorker(
                    DeviceExtension,
                    THRM_COOLING_LEVEL | THRM_INITIALIZE
                    );
                continue;

            }

            //
            // If the thermal zone mode needs updated, do it now
            //
            if (!(DeviceExtension->Thermal.Flags & THRM_MODE)) {

                DeviceExtension->Thermal.Flags |= THRM_BUSY | THRM_MODE;
                KeReleaseSpinLock (&DeviceExtension->Thermal.SpinLock, oldIrql);
                lockHeld = FALSE;

                status = ACPIGetNothingEvalIntegerAsync(
                    DeviceExtension,
                    PACKED_SCP,
                    thermal->Mode,
                    ACPIThermalComplete,
                    DeviceExtension
                    );
                if (status != STATUS_PENDING) {

                    ACPIThermalComplete(
                        NULL,
                        status,
                        NULL,
                        DeviceExtension
                        );

                }
                continue;

            }

            //
            // If the trip point infomation needs updated, get it. Note that
            // updating the trip points means that we also need to redo the
            // cooling level
            //
            if (!(DeviceExtension->Thermal.Flags & THRM_TRIP_POINTS)) {

                DeviceExtension->Thermal.Flags |= THRM_BUSY | THRM_TRIP_POINTS;
                ACPISetDeviceWorker( DeviceExtension, THRM_TRIP_POINTS );
                continue;

            }

            //
            // If the cooling level has changed,
            //
            if (!(DeviceExtension->Thermal.Flags & THRM_COOLING_LEVEL)) {

                DeviceExtension->Thermal.Flags |= THRM_BUSY | THRM_COOLING_LEVEL;
                ACPISetDeviceWorker (DeviceExtension, THRM_COOLING_LEVEL);
                continue;

            }

            //
            // Prevent the recursion that occurs when we complete an irp and
            // the completion routine is able to queue the IRP before we resume
            // the loop
            //
            if ( (DeviceExtension->Thermal.Flags & THRM_WAIT_FOR_NOTIFY) &&
                 (DeviceExtension->Thermal.Flags & THRM_TEMP) ) {

                break;

            }

            //
            // If we don't have a temp, get it
            //
            if (!(DeviceExtension->Thermal.Flags & THRM_TEMP)) {

                //
                // Is the temp object not present?
                //
#if DBG
                if (thermal->TempMethod == NULL) {

                    ACPIInternalError( ACPI_THERMAL );

                }
#endif

                thermal->Info.ThermalStamp += 1;
                DeviceExtension->Thermal.Flags |= THRM_BUSY | THRM_TEMP;
                KeReleaseSpinLock (&DeviceExtension->Thermal.SpinLock, oldIrql);
                lockHeld = FALSE;

                RtlZeroMemory (&thermal->Temp, sizeof(OBJDATA));

                thermal->Temp.dwDataType = OBJTYPE_UNKNOWN;
                status = AMLIAsyncEvalObject(
                    thermal->TempMethod,
                    &thermal->Temp,
                    0,
                    NULL,
                    ACPIThermalTempatureRead,
                    DeviceExtension
                    );

                if (status != STATUS_PENDING) {

                    ACPIThermalTempatureRead(
                        thermal->TempMethod,
                        status,
                        &thermal->Temp,
                        DeviceExtension
                        );

                }
                continue;

            }

            //
            // Everything is up to date.  Check for a pending irp to see if
            // we can complete it.
            //

            //
            // Call into a child function to determine if we have completed
            // any requests
            //
            doneRequests = ACPIThermalCompletePendingIrps(
                DeviceExtension,
                thermal
                );
            if (doneRequests) {

                continue;

            }
            break;

        }

        //
        // No longer in the serivce loop
        //
        DeviceExtension->Thermal.Flags &= ~THRM_IN_SERVICE_LOOP;

    }

    KeReleaseSpinLock (&DeviceExtension->Thermal.SpinLock, oldIrql);
    return ;
}

VOID
ACPIThermalPowerCallback (
    IN PDEVICE_EXTENSION    DeviceExtension,
    IN PVOID                Context,
    IN NTSTATUS             Status
    )
/*++

Routine Description:

    This is the routine that is called after we have sent an internal
    power request to the device

Arguments:

    DeviceExtension - the device that was set
    Context         - Not used
    Status          - Result

Return Value:

    None

--*/
{
    if (!NT_SUCCESS(Status)) {

        ACPIDevPrint( (
            ACPI_PRINT_CRITICAL,
            DeviceExtension,
            "ACPIThermalPowerCallBack: failed power setting %x\n",
            Status
            ) );

    }
}

NTSTATUS
ACPIThermalQueryWmiDataBlock(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN ULONG GuidIndex,
    IN ULONG InstanceIndex,
    IN ULONG InstanceCount,
    IN OUT PULONG InstanceLengthArray,
    IN ULONG BufferAvail,
    OUT PUCHAR Buffer
    )
/*++

Routine Description:

    This routine is a callback into the driver to query for the contents of
    all instances of a data block. When the driver has finished filling the
    data block it must call WmiCompleteRequest to complete the irp. The
    driver can return STATUS_PENDING if the irp cannot be completed
    immediately.

Arguments:

    DeviceObject is the device whose data block is being queried

    Irp is the Irp that makes this request

    GuidIndex is the index into the list of guids provided when the
        device registered

    InstanceCount is the number of instnaces expected to be returned for
        the data block.

    InstanceLengthArray is a pointer to an array of ULONG that returns the
        lengths of each instance of the data block. If this is NULL then
        there was not enough space in the output buffer to fufill the request
        so the irp should be completed with the buffer needed.

    BufferAvail on entry has the maximum size available to write the data
        blocks.

    Buffer on return is filled with the returned data blocks. Note that each
        instance of the data block must be aligned on a 8 byte boundry.


Return Value:

    status

--*/
{
    NTSTATUS                status;
    PDEVICE_EXTENSION       deviceExtension;
    ULONG                   sizeNeeded;
    PTHRM_INFO              info;
    PTHERMAL_INFORMATION    thermalInfo;
    PTHERMAL_INFORMATION    wmiThermalInfo;

    PAGED_CODE();

    deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);

    if (GuidIndex == 0) {

        //
        // ThermalZone temperature query
        //
        info = (PTHRM_INFO) deviceExtension->Thermal.Info;
        thermalInfo = &info->Info;

        wmiThermalInfo = (PTHERMAL_INFORMATION)Buffer;
        sizeNeeded = sizeof(THERMAL_INFORMATION);

        if (BufferAvail >= sizeNeeded) {

            // NOTE - Synchronize with thread getting this data
            *InstanceLengthArray = sizeNeeded;
            RtlCopyMemory(wmiThermalInfo, thermalInfo, sizeNeeded);
            status = STATUS_SUCCESS;

        } else {

            status = STATUS_BUFFER_TOO_SMALL;

        }

    } else {

        status = STATUS_WMI_GUID_NOT_FOUND;
        sizeNeeded = 0;

    }

    status = WmiCompleteRequest(
        DeviceObject,
        Irp,
        status,
        sizeNeeded,
        IO_NO_INCREMENT
        );
    return status;
}

NTSTATUS
ACPIThermalQueryWmiRegInfo(
    IN PDEVICE_OBJECT DeviceObject,
    OUT ULONG *RegFlags,
    OUT PUNICODE_STRING InstanceName,
    OUT PUNICODE_STRING *RegistryPath,
    OUT PUNICODE_STRING MofResourceName,
    OUT PDEVICE_OBJECT *Pdo
    )
/*++

Routine Description:

    This routine is a callback into the driver to retrieve information about
    the guids being registered.

    Implementations of this routine may be in paged memory

Arguments:

    DeviceObject is the device whose registration information is needed

    *RegFlags returns with a set of flags that describe all of the guids being
        registered for this device. If the device wants enable and disable
        collection callbacks before receiving queries for the registered
        guids then it should return the WMIREG_FLAG_EXPENSIVE flag. Also the
        returned flags may specify WMIREG_FLAG_INSTANCE_PDO in which case
        the instance name is determined from the PDO associated with the
        device object. Note that the PDO must have an associated devnode. If
        WMIREG_FLAG_INSTANCE_PDO is not set then Name must return a unique
        name for the device. These flags are ORed into the flags specified
        by the GUIDREGINFO for each guid.

    InstanceName returns with the instance name for the guids if
        WMIREG_FLAG_INSTANCE_PDO is not set in the returned *RegFlags. The
        caller will call ExFreePool with the buffer returned.

    *RegistryPath returns with the registry path of the driver. This is
        required

    MofResourceName returns with the name of the MOF resource attached to
        the binary file. If the driver does not have a mof resource attached
        then this can be returned unmodified. If a value is returned then
        it is NOT freed.

    *Pdo returns with the device object for the PDO associated with this
        device if the WMIREG_FLAG_INSTANCE_PDO flag is retured in
        *RegFlags.

Return Value:

    status

--*/
{
    PAGED_CODE();

    if (AcpiRegistryPath.Buffer != NULL) {

        *RegistryPath = &AcpiRegistryPath;

    } else {

        *RegistryPath = NULL;

    }

    *RegFlags = WMIREG_FLAG_INSTANCE_PDO;
    *Pdo = DeviceObject;
    return STATUS_SUCCESS;
}

NTSTATUS
ACPIThermalStartDevice (
    IN  PDEVICE_OBJECT  DeviceObject,
    IN  PIRP            Irp
    )
/*++

Routine Description:

    This routine is called to start the thermal zone

Arguments:

    DeviceObject    - The device that is starting up
    Irp             - The request

Return Value:

    NTSTATUS

--*/
{
    NTSTATUS            status;
    PDEVICE_EXTENSION   deviceExtension;
    PWMILIB_CONTEXT     wmilibContext;

    deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);

    ACPIDevPrint( (
        ACPI_PRINT_IRP,
        deviceExtension,
        "(0x%08lx): IRP_MN_START_DEVICE\n",
        Irp
        ) );

    status = ACPIInternalSetDeviceInterface (
        DeviceObject,
        (LPGUID) &GUID_DEVICE_THERMAL_ZONE
        );

    if (!NT_SUCCESS(status)) {

        ACPIDevPrint( (
            ACPI_PRINT_FAILURE,
            deviceExtension,
            "ACPIThermalStartDevice -> SetDeviceInterface = 0x%08lx\n",
            status
            ) );
        goto ACPIThermalStartDeviceExit;

    }

    ACPIRegisterForDeviceNotifications(
        DeviceObject,
        (PDEVICE_NOTIFY_CALLBACK) ACPIThermalEvent,
        (PVOID) DeviceObject
        );

    //
    // Initialize device object for WMILIB
    //
    wmilibContext = ExAllocatePoolWithTag(
        PagedPool,
        sizeof(WMILIB_CONTEXT),
        ACPI_THERMAL_POOLTAG
        );
    if (wmilibContext == NULL) {

        status = STATUS_INSUFFICIENT_RESOURCES;
        goto ACPIThermalStartDeviceExit;

    }

    RtlZeroMemory(wmilibContext, sizeof(WMILIB_CONTEXT));
    wmilibContext->GuidCount = ACPIThermalGuidCount;
    wmilibContext->GuidList = &ACPIThermalGuidList;
    wmilibContext->QueryWmiRegInfo = ACPIThermalQueryWmiRegInfo;
    wmilibContext->QueryWmiDataBlock = ACPIThermalQueryWmiDataBlock;
    deviceExtension->Thermal.WmilibContext = wmilibContext;

    //
    // Register for WMI events
    //
    status = IoWMIRegistrationControl(
        DeviceObject,
        WMIREG_ACTION_REGISTER
        );
    if (!NT_SUCCESS(status)) {

        deviceExtension->Thermal.WmilibContext = NULL;
        ExFreePool(wmilibContext);
        goto ACPIThermalStartDeviceExit;

    }

    //
    // Mark the device as started
    //
    deviceExtension->DeviceState = Started;

    //
    // Request that the device go to the D0 state
    //  Note: that we don't block on this call, since we assume that
    //        we can process thermal events asynchronously from being in
    //        the D0 state. However, there may be a future occasion where
    //        this is not true, so this makes the code more ready to handle
    //        that case
    //
    status = ACPIDeviceInternalDeviceRequest(
        deviceExtension,
        PowerDeviceD0,
        NULL,
        NULL,
        0
        );
    if (status == STATUS_PENDING) {

        status = STATUS_SUCCESS;

    }

    //
    // Start the thermal engine
    //
    ACPIThermalLoop( deviceExtension, THRM_TRIP_POINTS | THRM_MODE);

ACPIThermalStartDeviceExit:

    Irp->IoStatus.Status = status;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );
    return status;
}

VOID
EXPORT
ACPIThermalTempatureRead (
    IN PNSOBJ               AcpiObject,
    IN NTSTATUS             Status,
    IN POBJDATA             Result  OPTIONAL,
    IN PVOID                Context
    )
/*++

Routine Description:

    This routine is called to read the temperature. It is used a callback to
    an interpreter call

Arguments:

    AcpiObject  - The object that was executed
    Status      - The status of the execution
    Result      - The result of the execution
    Context     - The device extension

Return Value:

    NTSTATUS

--*/
{
    PTHRM_INFO          Thrm;
    PDEVICE_EXTENSION   deviceExtension;

    deviceExtension = Context;

    if (NT_SUCCESS(Status)) {

        ASSERT (Result->dwDataType == OBJTYPE_INTDATA);
        Thrm = deviceExtension->Thermal.Info;
        Thrm->Info.CurrentTemperature = (ULONG)Result->uipDataValue;
        AMLIFreeDataBuffs (Result, 1);

        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            deviceExtension,
            KeQueryInterruptTime(),
            "Current Temperature is %d.%dK\n",
            (Thrm->Info.CurrentTemperature / 10 ),
            (Thrm->Info.CurrentTemperature % 10 )
            ) );

    }
    ACPIThermalLoop (deviceExtension, THRM_BUSY);
}

VOID
ACPIThermalWorker (
    IN PDEVICE_EXTENSION    DeviceExtension,
    IN ULONG                Events
    )
/*++

Routine Description:

    Worker thread for thermal regions

Arguments:

    DeviceExtension - The device extension that we are manipulating
    Events          - What just happened

Return Value:

    None

--*/
{
    BOOLEAN             TurnOn;
    PTHRM_INFO          Thrm;
    NTSTATUS            Status;
    PNSOBJ              ThrmObj;
    PNSOBJ              ALobj;
    OBJDATA             ALPackage;
    OBJDATA             ALElement;
    PNSOBJ              ACDevObj;
    ULONG               Index;
    ULONG               Level;
    ULONG               PackageSize;
    ULONGLONG           currentTime;

    PAGED_CODE();

#if DBG
    currentTime = KeQueryInterruptTime();
#endif

    Thrm = DeviceExtension->Thermal.Info;
    ThrmObj = DeviceExtension->AcpiObject;

    //
    // Initialization code
    //
    if (Events & THRM_INITIALIZE) {

        ULONG   names[10] = {
                    PACKED_AL0,
                    PACKED_AL1,
                    PACKED_AL2,
                    PACKED_AL3,
                    PACKED_AL4,
                    PACKED_AL5,
                    PACKED_AL6,
                    PACKED_AL7,
                    PACKED_AL8,
                    PACKED_AL9,
                    };

        //
        // Start the system in PASSIVE mode
        //
        Thrm->Mode = 1;

        //
        // Fetch all of the objects associated with each cooling level
        //
        for (Level = 0; Level < 10; Level++) {

            //
            // Find this level's active list
            //
            ALobj = ACPIAmliGetNamedChild(
                ThrmObj,
                names[Level]
                );
            if (ALobj == NULL) {

                break;

            }

            //
            // Remember that we have this object
            //
            Thrm->ActiveList[Level] = ALobj;

        }

    }

    //
    // Do this before we update the trips points
    //
    if ( (Events & THRM_COOLING_LEVEL) ) {

        RtlZeroMemory (&ALPackage, sizeof(OBJDATA));
        RtlZeroMemory (&ALElement, sizeof(OBJDATA));

        for (Level=0; Level < 10; Level++) {

            //
            // Is there a cooling object?
            //
            ALobj = Thrm->ActiveList[Level];
            if (ALobj == NULL) {

                break;
            }

            //
            // Evalute the list to its package
            //
            Status = AMLIEvalNameSpaceObject(
                ALobj,
                &ALPackage,
                0,
                NULL
                );
            if (!NT_SUCCESS(Status)) {

                break;

            }

            //
            // Remember how large the package is
            //
            PackageSize = ((PPACKAGEOBJ) ALPackage.pbDataBuff)->dwcElements;

            //
            // Walk the names in the package
            //
            for (Index = 0; Index < PackageSize; Index += 1) {

                //
                // Grab the object name
                Status = AMLIEvalPkgDataElement(
                    &ALPackage,
                    Index,
                    &ALElement
                    );
                if (!NT_SUCCESS(Status)) {

                    break;

                }

                //
                // Determine if we are going to the device on or off
                //
                TurnOn = (Level >= Thrm->CoolingLevel);

                //
                // Tell the world
                //
#if DBG
                ACPIThermalPrint( (
                    ACPI_PRINT_THERMAL,
                    DeviceExtension,
                    currentTime,
                    "ACPIThermalWorker: Turn %s %s\n",
                    TurnOn ? "on " : "off",
                    ALElement.pbDataBuff
                    ) );
#endif

                //
                // Find this device of this name
                //
                Status = AMLIGetNameSpaceObject(
                    ALElement.pbDataBuff,
                    ThrmObj,
                    &ACDevObj,
                    0
                    );
                AMLIFreeDataBuffs (&ALElement, 1);
                if (!NT_SUCCESS(Status) ||  !ACDevObj->Context) {

                    break;

                }

                //
                // Turn it on/off
                //
                ACPIDeviceInternalDeviceRequest (
                    (PDEVICE_EXTENSION) ACDevObj->Context,
                    TurnOn ? PowerDeviceD0 : PowerDeviceD3,
                    ACPIThermalPowerCallback,
                    NULL,
                    0
                    );

            }
            AMLIFreeDataBuffs (&ALPackage, 1);

        }

    }

    //
    // If the trip points need to be re-freshed, go read them
    //
    if (Events & THRM_TRIP_POINTS) {

        ULONG   names[10] = {
                    PACKED_AC0,
                    PACKED_AC1,
                    PACKED_AC2,
                    PACKED_AC3,
                    PACKED_AC4,
                    PACKED_AC5,
                    PACKED_AC6,
                    PACKED_AC7,
                    PACKED_AC8,
                    PACKED_AC9,
                    };

        //
        // Get the thermal constants, passive & critical values
        //
        ACPIGetIntegerSync(
            DeviceExtension,
            PACKED_TC1,
            &Thrm->Info.ThermalConstant1,
            NULL
            );
#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            DeviceExtension,
            currentTime,
            "ACPIThermalWorker - ThermalConstant1 = %x\n",
            Thrm->Info.ThermalConstant1
            ) );
#endif
        ACPIGetIntegerSync(
            DeviceExtension,
            PACKED_TC2,
            &Thrm->Info.ThermalConstant2,
            NULL
            );
#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            DeviceExtension,
            currentTime,
            "ACPIThermalWorker - ThermalConstant2 = %x\n",
            Thrm->Info.ThermalConstant2
            ) );
#endif
        ACPIGetIntegerSync(
            DeviceExtension,
            PACKED_PSV,
            &Thrm->Info.PassiveTripPoint,
            NULL
            );
#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            DeviceExtension,
            currentTime,
            "ACPIThermalWorker - PassiveTripPoint = %d.%dK\n",
            (Thrm->Info.PassiveTripPoint / 10),
            (Thrm->Info.PassiveTripPoint % 10)
            ) );
#endif
        ACPIGetIntegerSync(
            DeviceExtension,
            PACKED_CRT,
            &Thrm->Info.CriticalTripPoint,
            NULL
            );
#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            DeviceExtension,
            currentTime,
            "ACPIThermalWorker - CriticalTripPoint = %d.%dK\n",
            (Thrm->Info.CriticalTripPoint / 10),
            (Thrm->Info.CriticalTripPoint % 10)
            ) );
#endif
        ACPIGetIntegerSync(
            DeviceExtension,
            PACKED_TSP,
            &Thrm->Info.SamplingPeriod,
            NULL
            );
#if DBG
        ACPIThermalPrint( (
            ACPI_PRINT_THERMAL,
            DeviceExtension,
            currentTime,
            "ACPIThermalWorker - SamplingPeriod = %x\n",
            Thrm->Info.SamplingPeriod
            ) );
#endif

        //
        // Get the active cooling limits
        //
        for (Level=0; Level < 10; Level++) {

            Status = ACPIGetIntegerSync(
                DeviceExtension,
                names[Level],
                &Thrm->Info.ActiveTripPoint[Level],
                NULL
                );
            if (!NT_SUCCESS(Status)) {

                break;

            }
#if DBG
            ACPIThermalPrint( (
                ACPI_PRINT_THERMAL,
                DeviceExtension,
                currentTime,
                "ACPIThermalWorker - Active Cooling Level %x = %d.%dK\n",
                Level,
                (Thrm->Info.ActiveTripPoint[Level] / 10),
                (Thrm->Info.ActiveTripPoint[Level] % 10)
                ) );
#endif

        }
        Thrm->Info.ActiveTripPointCount = (UCHAR) Level;

        //
        // Clean these variables for reuse
        //
        RtlZeroMemory (&ALPackage, sizeof(OBJDATA));
        RtlZeroMemory (&ALElement, sizeof(OBJDATA));

        //
        // Assume an affinity of 0
        //
        Thrm->Info.Processors = 0;

        //
        // Get the passive cooling affinity object
        //
        ALobj = ACPIAmliGetNamedChild(
            ThrmObj,
            PACKED_PSL
            );
        if (ALobj != NULL) {

            //
            // Evaluate the processor affinity object
            //
            Status = AMLIEvalNameSpaceObject(
                ALobj,
                &ALPackage,
                0,
                NULL
                );
            if (!NT_SUCCESS(Status)) {

                goto ACPIThermalWorkerExit;

            }

            //
            // Remember how large the package is
            //
            PackageSize = ((PPACKAGEOBJ) ALPackage.pbDataBuff)->dwcElements;

            //
            // Walk the elements in the package
            //
            for (Index = 0; Index < PackageSize ;Index++) {

                Status = AMLIEvalPkgDataElement(
                    &ALPackage,
                    Index,
                    &ALElement
                    );
                if (!NT_SUCCESS(Status)) {

                    break;

                }

                //
                // Find this device of this name
                //
                Status = AMLIGetNameSpaceObject(
                    ALElement.pbDataBuff,
                    NULL,
                    &ACDevObj,
                    0
                    );

                //
                // No longer need this information
                //
                AMLIFreeDataBuffs (&ALElement, 1);

                //
                // Did we find what we wanted?
                //
                if (!NT_SUCCESS(Status) ) {

                    break;

                }

                //
                // Get the correct affinity mask. We call another
                // function since that one requires a spinlock which
                // don't want to take in this worker function
                //
                ACPIThermalCalculateProcessorMask( ACDevObj, Thrm );

            }

            //
            // We are done with the package
            //
            AMLIFreeDataBuffs (&ALPackage, 1);

        }

    }

ACPIThermalWorkerExit:

    //
    // done, check for next work
    //
    ACPIThermalLoop (DeviceExtension, THRM_TEMP | THRM_BUSY);
}


NTSTATUS
ACPIThermalWmi(
    IN PDEVICE_OBJECT   DeviceObject,
    IN PIRP             Irp
    )
{
    NTSTATUS                status;
    PDEVICE_EXTENSION       deviceExtension = ACPIInternalGetDeviceExtension(DeviceObject);
    PIO_STACK_LOCATION      irpSp;
    PWMILIB_CONTEXT         wmilibContext;
    SYSCTL_IRP_DISPOSITION  disposition;

    wmilibContext = deviceExtension->Thermal.WmilibContext;
    irpSp = IoGetCurrentIrpStackLocation(Irp);

    status = WmiSystemControl(
        wmilibContext,
        DeviceObject,
        Irp,
        &disposition
        );

    switch (disposition) {

        case IrpProcessed:
            break;
        case IrpNotCompleted:
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
            break;
        case IrpNotWmi:
        case IrpForward:
        default:
            status = ACPIDispatchForwardIrp(DeviceObject, Irp);
            break;
    }

    return status;
}