/*
 * PROJECT:         ReactOS ACPI-Compliant Control Method Battery
 * LICENSE:         BSD - See COPYING.ARM in the top level directory
 * FILE:            boot/drivers/bus/acpi/cmbatt/cmbatt.c
 * PURPOSE:         Main Initialization Code and IRP Handling
 * PROGRAMMERS:     ReactOS Portable Systems Group
 */

/* INCLUDES *******************************************************************/

#include "cmbatt.h"

#include <debug.h>

/* GLOBALS ********************************************************************/

ULONG CmBattDebug;
PCALLBACK_OBJECT CmBattPowerCallBackObject;
PVOID CmBattPowerCallBackRegistration;
UNICODE_STRING GlobalRegistryPath;
KTIMER CmBattWakeDpcTimerObject;
KDPC CmBattWakeDpcObject;
PDEVICE_OBJECT AcAdapterPdo;
LARGE_INTEGER CmBattWakeDpcDelay;

/* FUNCTIONS ******************************************************************/

VOID
NTAPI
CmBattPowerCallBack(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
                    IN ULONG Action,
                    IN ULONG Value)
{
    BOOLEAN Cancelled;
    PDEVICE_OBJECT DeviceObject;
    if (CmBattDebug & 0x10)
        DbgPrint("CmBattPowerCallBack: action: %d, value: %d \n", Action, Value);

    /* Check if a transition is going to happen */
    if (Action == PO_CB_SYSTEM_STATE_LOCK)
    {
        /* We have just re-entered S0: call the wake DPC in 10 seconds */
        if (Value == 1)
        {
            if (CmBattDebug & 0x10)
                DbgPrint("CmBattPowerCallBack: Calling CmBattWakeDpc after 10 seconds.\n");
            Cancelled = KeSetTimer(&CmBattWakeDpcTimerObject, CmBattWakeDpcDelay, &CmBattWakeDpcObject);
            if (CmBattDebug & 0x10)
                DbgPrint("CmBattPowerCallBack: timerCanceled = %d.\n", Cancelled);
        }
        else if (Value == 0)
        {
            /* We are exiting the S0 state: loop all devices to set the delay flag */
            if (CmBattDebug & 0x10)
                DbgPrint("CmBattPowerCallBack: Delaying Notifications\n");
            for (DeviceObject = DeviceExtension->DeviceObject;
                 DeviceObject;
                 DeviceObject = DeviceObject->NextDevice)
            {
                /* Set the delay flag */
                DeviceExtension = DeviceObject->DeviceExtension;
                DeviceExtension->DelayNotification = TRUE;
            }
        }
        else if (CmBattDebug & 0x10)
        {
            /* Unknown value */
            DbgPrint("CmBattPowerCallBack: unknown argument2 = %08x\n", Value);
        }
    }
}

VOID
NTAPI
CmBattWakeDpc(IN PKDPC Dpc,
              IN PCMBATT_DEVICE_EXTENSION FdoExtension,
              IN PVOID SystemArgument1,
              IN PVOID SystemArgument2)
{
    PDEVICE_OBJECT CurrentObject;
    BOOLEAN AcNotify = FALSE;
    PCMBATT_DEVICE_EXTENSION DeviceExtension;
    ULONG ArFlag;
    if (CmBattDebug & 2) DbgPrint("CmBattWakeDpc: Entered.\n");

    /* Loop all device objects */
    for (CurrentObject = FdoExtension->DeviceObject;
         CurrentObject;
         CurrentObject = CurrentObject->NextDevice)
    {
        /* Turn delay flag off, we're back in S0 */
        DeviceExtension = CurrentObject->DeviceExtension;
        DeviceExtension->DelayNotification = 0;

        /* Check if this is an AC adapter */
        if (DeviceExtension->FdoType == CmBattAcAdapter)
        {
            /* Was there a pending notify? */
            if (DeviceExtension->ArFlag & CMBATT_AR_NOTIFY)
            {
                /* We'll send a notify on the next pass */
                AcNotify = TRUE;
                DeviceExtension->ArFlag = 0;
                if (CmBattDebug & 0x20)
                    DbgPrint("CmBattWakeDpc: AC adapter notified\n");
            }
        }
    }

    /* Loop the device objects again */
    for (CurrentObject = FdoExtension->DeviceObject;
         CurrentObject;
         CurrentObject = CurrentObject->NextDevice)
    {
        /* Check if this is a battery */
        DeviceExtension = CurrentObject->DeviceExtension;
        if (DeviceExtension->FdoType == CmBattBattery)
        {
            /* Check what ARs are pending */
            ArFlag = DeviceExtension->ArFlag;
            if (CmBattDebug & 0x20)
                DbgPrint("CmBattWakeDpc: Performing delayed ARs: %01x\n", ArFlag);

            /* Insert notification, clear the lock value */
            if (ArFlag & CMBATT_AR_INSERT) InterlockedExchange(&DeviceExtension->ArLockValue, 0);

            /* Removal, clear the battery tag */
            if (ArFlag & CMBATT_AR_REMOVE) DeviceExtension->Tag = 0;

            /* Notification (or AC/DC adapter change from first pass above) */
            if ((ArFlag & CMBATT_AR_NOTIFY) || (AcNotify))
            {
                /* Notify the class driver */
                BatteryClassStatusNotify(DeviceExtension->ClassData);
            }
        }
    }
}

VOID
NTAPI
CmBattNotifyHandler(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
                    IN ULONG NotifyValue)
{
    ULONG ArFlag;
    PCMBATT_DEVICE_EXTENSION FdoExtension;
    PDEVICE_OBJECT DeviceObject;

    if (CmBattDebug & (CMBATT_ACPI_ASSERT | CMBATT_PNP_INFO))
        DbgPrint("CmBattNotifyHandler: CmBatt 0x%08x Type %d Number %d Notify Value: %x\n",
                 DeviceExtension,
                 DeviceExtension->FdoType,
                 DeviceExtension->DeviceId,
                 NotifyValue);

    /* Check what kind of notification was received */
    switch (NotifyValue)
    {
        /* ACPI Specification says is sends a "Bus Check" when power source changes */
        case ACPI_BUS_CHECK:

            /* We treat it as possible physical change */
            DeviceExtension->ArFlag |= (CMBATT_AR_NOTIFY | CMBATT_AR_INSERT);
            if ((DeviceExtension->Tag) &&
                (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_WARNING)))
                DbgPrint("CmBattNotifyHandler: Received battery #%x insertion, but tag was not invalid.\n",
                         DeviceExtension->DeviceId);
            break;

        /* Status of the battery has changed */
        case ACPI_BATT_NOTIFY_STATUS:

            /* All we'll do is notify the class driver */
            DeviceExtension->ArFlag |= CMBATT_AR_NOTIFY;
            break;

        /* Information on the battery has changed, such as physical presence */
        case ACPI_DEVICE_CHECK:
        case ACPI_BATT_NOTIFY_INFO:

            /* Reset all state and let the class driver re-evaluate it all */
            DeviceExtension->ArFlag |= (CMBATT_AR_NOTIFY |
                                        CMBATT_AR_INSERT |
                                        CMBATT_AR_REMOVE);
            break;

        default:

            if (CmBattDebug & CMBATT_PNP_INFO)
                DbgPrint("CmBattNotifyHandler: Unknown Notify Value: %x\n", NotifyValue);
    }

    /* Check if we're supposed to delay the notification till later */
    if (DeviceExtension->DelayNotification)
    {
        /* We'll handle this when we get a status query later on */
        if (CmBattDebug & CMBATT_PNP_INFO)
            DbgPrint("CmBattNotifyHandler: Notification delayed: ARs = %01x\n",
                      DeviceExtension->ArFlag);
        return;
    }

    /* We're going to handle this now */
    if (CmBattDebug & CMBATT_PNP_INFO)
        DbgPrint("CmBattNotifyHandler: Performing ARs: %01x\n", DeviceExtension->ArFlag);

    /* Check if this is a battery or AC adapter notification */
    if (DeviceExtension->FdoType == CmBattBattery)
    {
        /* Reset the current trip point */
        DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;

        /* Check what ARs have to be done */
        ArFlag = DeviceExtension->ArFlag;

        /* New battery inserted, reset lock value */
        if (ArFlag & CMBATT_AR_INSERT) InterlockedExchange(&DeviceExtension->ArLockValue, 0);

        /* Check if the battery may have been removed */
        if (ArFlag & CMBATT_AR_REMOVE) DeviceExtension->Tag = 0;

        /* Check if there's been any sort of change to the battery */
        if (ArFlag & CMBATT_AR_NOTIFY)
        {
            /* We'll probably end up re-evaluating _BIF and _BST */
            DeviceExtension->NotifySent = TRUE;
            BatteryClassStatusNotify(DeviceExtension->ClassData);
        }
    }
    else if (DeviceExtension->ArFlag & CMBATT_AR_NOTIFY)
    {
        /* The only known notification is AC/DC change. Loop device objects. */
        for (DeviceObject = DeviceExtension->FdoDeviceObject->DriverObject->DeviceObject;
             DeviceObject;
             DeviceObject = DeviceObject->NextDevice)
        {
            /* Is this a battery? */
            FdoExtension = DeviceObject->DeviceExtension;
            if (FdoExtension->FdoType == CmBattBattery)
            {
                /* Send a notification to the class driver */
                FdoExtension->NotifySent = TRUE;
                BatteryClassStatusNotify(FdoExtension->ClassData);
            }
        }
    }

    /* ARs have been processed */
    DeviceExtension->ArFlag = 0;
}

VOID
NTAPI
CmBattUnload(IN PDRIVER_OBJECT DriverObject)
{
    if (CmBattDebug & CMBATT_GENERIC_INFO) DPRINT("CmBattUnload: \n");

    /* Check if we have a registered power callback */
    if (CmBattPowerCallBackObject)
    {
        /* Get rid of it */
        ExUnregisterCallback(CmBattPowerCallBackRegistration);
        ObDereferenceObject(CmBattPowerCallBackObject);
    }

    /* Free the registry buffer if it exists */
    if (GlobalRegistryPath.Buffer) ExFreePool(GlobalRegistryPath.Buffer);

    /* Make sure we don't still have references to the DO */
    if ((DriverObject->DeviceObject) && (CmBattDebug & CMBATT_GENERIC_WARNING))
    {
        DbgPrint("Unload called before all devices removed.\n");
    }
}

NTSTATUS
NTAPI
CmBattVerifyStaticInfo(PCMBATT_DEVICE_EXTENSION DeviceExtension,
                       ULONG BatteryTag)
{
    ACPI_BIF_DATA BifData;
    PBATTERY_INFORMATION Info = &DeviceExtension->BatteryInformation;
    NTSTATUS Status;

    Status = CmBattGetBifData(DeviceExtension, &BifData);
    if (NT_SUCCESS(Status))
    {
        RtlZeroMemory(Info, sizeof(*Info));
        Info->Capabilities = BATTERY_SYSTEM_BATTERY;
        Info->Technology = BifData.BatteryTechnology;
        RtlCopyMemory(Info->Chemistry, BifData.BatteryType, 4);
        // FIXME: take from _BIX method: Info->CycleCount
        DeviceExtension->BifData = BifData;

        if (BifData.PowerUnit == 1)
        {
            DPRINT1("FIXME: need to convert mAh into mWh\n");
            Info->DesignedCapacity = BATTERY_UNKNOWN_CAPACITY;
            Info->FullChargedCapacity = BATTERY_UNKNOWN_CAPACITY;
            Info->DefaultAlert1 = BATTERY_UNKNOWN_CAPACITY;
            Info->DefaultAlert2 = BATTERY_UNKNOWN_CAPACITY;
        }
        else
        {
            Info->DesignedCapacity = BifData.DesignCapacity;
            Info->FullChargedCapacity = BifData.LastFullCapacity;
            Info->DefaultAlert1 = BifData.DesignCapacityLow;
            Info->DefaultAlert2 = BifData.DesignCapacityWarning;
        }
    }

    return Status;
}

NTSTATUS
NTAPI
CmBattOpenClose(IN PDEVICE_OBJECT DeviceObject,
                IN PIRP Irp)
{
    NTSTATUS Status = STATUS_SUCCESS;
    PIO_STACK_LOCATION IoStackLocation;
    UCHAR Major;
    ULONG Count;
    PCMBATT_DEVICE_EXTENSION DeviceExtension;
    PAGED_CODE();
    if (CmBattDebug & CMBATT_GENERIC_INFO) DPRINT("CmBattOpenClose\n");

    /* Grab the device extension and lock it */
    DeviceExtension = DeviceObject->DeviceExtension;
    ExAcquireFastMutex(&DeviceExtension->FastMutex);

    /* Check if someone is trying to open a device that doesn't exist yet */
    Count = DeviceExtension->HandleCount;
    if (Count == 0xFFFFFFFF)
    {
        /* Fail the request */
        Status = STATUS_NO_SUCH_DEVICE;
        if (CmBattDebug & CMBATT_PNP_INFO)
        {
            DbgPrint("CmBattOpenClose: Failed (UID = %x)(device being removed).\n",
                     DeviceExtension->Tag);
        }
        goto Complete;
    }

    /* Check if this is an open or close */
    IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
    Major = IoStackLocation->MajorFunction;
    if (Major == IRP_MJ_CREATE)
    {
        /* Increment the open count */
        DeviceExtension->HandleCount = Count + 1;
        if (CmBattDebug & CMBATT_PNP_INFO)
        {
            DbgPrint("CmBattOpenClose: Open (DeviceNumber = %x)(count = %x).\n",
                     DeviceExtension->DeviceId, Count + 1);
        }
    }
    else if (Major == IRP_MJ_CLOSE)
    {
        /* Decrement the open count */
        DeviceExtension->HandleCount = Count - 1;
        if (CmBattDebug & CMBATT_PNP_INFO)
        {
            DbgPrint("CmBattOpenClose: Close (DeviceNumber = %x)(count = %x).\n",
                     DeviceExtension->DeviceId, Count + 1);
        }
    }

Complete:
    /* Release lock and complete request */
    ExReleaseFastMutex(&DeviceExtension->FastMutex);
    Irp->IoStatus.Status = Status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return Status;
}

NTSTATUS
NTAPI
CmBattIoctl(IN PDEVICE_OBJECT DeviceObject,
            IN PIRP Irp)
{
    PCMBATT_DEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
    NTSTATUS Status;
    PIO_STACK_LOCATION IoStackLocation;
    ULONG IoControlCode, OutputBufferLength, InputBufferLength;
    PAGED_CODE();
    if (CmBattDebug & 2) DbgPrint("CmBattIoctl\n");

    /* Acquire the remove lock */
    Status = IoAcquireRemoveLock(&DeviceExtension->RemoveLock, Irp);
    if (!NT_SUCCESS(Status))
    {
        /* It's too late, fail */
        Irp->IoStatus.Status = STATUS_DEVICE_REMOVED;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_DEVICE_REMOVED;
    }

    /* There's nothing to do for an AC adapter */
    if (DeviceExtension->FdoType == CmBattAcAdapter)
    {
        /* Pass it down, and release the remove lock */
        IoSkipCurrentIrpStackLocation(Irp);
        Status = IoCallDriver(DeviceExtension->AttachedDevice, Irp);
        IoReleaseRemoveLock(&DeviceExtension->RemoveLock, Irp);
        return Status;
    }

    /* Send to class driver */
    Status = BatteryClassIoctl(DeviceExtension->ClassData, Irp);
    if (Status == STATUS_NOT_SUPPORTED)
    {
        /* Read IOCTL information from IRP stack */
        IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
        IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
        OutputBufferLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
        InputBufferLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
        if (CmBattDebug & 4)
            DbgPrint("CmBattIoctl: Received  Direct Access IOCTL %x\n", IoControlCode);

        /* Handle internal IOCTLs */
        switch (IoControlCode)
        {
            case IOCTL_BATTERY_QUERY_UNIQUE_ID:

                /* Data is 4 bytes long */
                if (OutputBufferLength == sizeof(ULONG))
                {
                    /* Query it */
                    Status = CmBattGetUniqueId(DeviceExtension->PdoDeviceObject,
                                               Irp->AssociatedIrp.SystemBuffer);
                    if (NT_SUCCESS(Status)) Irp->IoStatus.Information = sizeof(ULONG);
                }
                else
                {
                    /* Buffer size invalid */
                    Status = STATUS_INVALID_BUFFER_SIZE;
                }
                break;

            case IOCTL_BATTERY_QUERY_STA:

                /* Data is 4 bytes long */
                if (OutputBufferLength == sizeof(ULONG))
                {
                    /* Query it */
                    Status = CmBattGetStaData(DeviceExtension->PdoDeviceObject,
                                              Irp->AssociatedIrp.SystemBuffer);
                    if (NT_SUCCESS(Status)) Irp->IoStatus.Information = sizeof(ULONG);
                }
                else
                {
                    /* Buffer size invalid */
                    Status = STATUS_INVALID_BUFFER_SIZE;
                }
                break;

            case IOCTL_BATTERY_QUERY_PSR:

                /* Data is 4 bytes long */
                if (OutputBufferLength == sizeof(ULONG))
                {
                    /* Do we have an AC adapter? */
                    if (AcAdapterPdo)
                    {
                        /* Query it */
                        Status = CmBattGetPsrData(AcAdapterPdo,
                                                  Irp->AssociatedIrp.SystemBuffer);
                        if (NT_SUCCESS(Status)) Irp->IoStatus.Information = sizeof(ULONG);
                    }
                    else
                    {
                        /* No adapter, just a battery, so fail */
                        Status = STATUS_NO_SUCH_DEVICE;
                    }
                }
                else
                {
                    /* Buffer size invalid */
                    Status = STATUS_INVALID_BUFFER_SIZE;
                }
                break;

            case IOCTL_BATTERY_SET_TRIP_POINT:

                /* Data is 4 bytes long */
                if (InputBufferLength == sizeof(ULONG))
                {
                    /* Query it */
                    Status = CmBattSetTripPpoint(DeviceExtension,
                                                 *(PULONG)Irp->AssociatedIrp.SystemBuffer);
                    Irp->IoStatus.Information = 0;
                }
                else
                {
                    /* Buffer size invalid */
                    Status = STATUS_INVALID_BUFFER_SIZE;
                }
                break;

            case IOCTL_BATTERY_QUERY_BIF:

                /* Data is 1060 bytes long */
                if (OutputBufferLength == sizeof(ACPI_BIF_DATA))
                {
                    /* Query it */
                    Status = CmBattGetBifData(DeviceExtension,
                                              Irp->AssociatedIrp.SystemBuffer);
                    if (NT_SUCCESS(Status)) Irp->IoStatus.Information = sizeof(ACPI_BIF_DATA);
                }
                else
                {
                    /* Buffer size invalid */
                    Status = STATUS_INVALID_BUFFER_SIZE;
                }
                break;

            case IOCTL_BATTERY_QUERY_BST:

                /* Data is 16 bytes long */
                if (OutputBufferLength == sizeof(ACPI_BST_DATA))
                {
                    /* Query it */
                    Status = CmBattGetBstData(DeviceExtension,
                                              Irp->AssociatedIrp.SystemBuffer);
                    if (NT_SUCCESS(Status)) Irp->IoStatus.Information = sizeof(ACPI_BST_DATA);
                }
                else
                {
                    /* Buffer size invalid */
                    Status = STATUS_INVALID_BUFFER_SIZE;
                }
                break;

            default:

                /* Unknown, let us pass it on to ACPI */
                if (CmBattDebug & 0xC)
                    DbgPrint("CmBattIoctl: Unknown IOCTL %x\n", IoControlCode);
                break;
        }

        /* Did someone pick it up? */
        if (Status != STATUS_NOT_SUPPORTED)
        {
            /* Complete the request */
            Irp->IoStatus.Status = Status;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);
        }
        else
        {
            /* Still unsupported, try ACPI */
            IoSkipCurrentIrpStackLocation(Irp);
            Status = IoCallDriver(DeviceExtension->AttachedDevice, Irp);
        }
    }

    /* Release the remove lock and return status */
    IoReleaseRemoveLock(&DeviceExtension->RemoveLock, Irp);
    return Status;
}

NTSTATUS
NTAPI
CmBattQueryTag(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
               OUT PULONG Tag)
{
    PDEVICE_OBJECT PdoDevice;
    ULONG StaData;
    ULONG NewTag;
    NTSTATUS Status;
    PAGED_CODE();
    if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
      DbgPrint("CmBattQueryTag - Tag (%d), Battery %x, Device %d\n",
                *Tag, DeviceExtension, DeviceExtension->DeviceId);

    /* Get PDO and clear notification flag */
    PdoDevice = DeviceExtension->PdoDeviceObject;
    DeviceExtension->NotifySent = 0;

    /* Get _STA from PDO (we need the machine status, not the battery status) */
    Status = CmBattGetStaData(PdoDevice, &StaData);
    if (NT_SUCCESS(Status))
    {
        /* Is a battery present? */
        if (StaData & ACPI_STA_BATTERY_PRESENT)
        {
            /* Do we not have a tag yet? */
            if (!DeviceExtension->Tag)
            {
                /* Set the new tag value, reset tags if we reached the maximum */
                NewTag = DeviceExtension->TagData;
                if (DeviceExtension->TagData++ == 0xFFFFFFFF) NewTag = 1;
                DeviceExtension->Tag = NewTag;
                if (CmBattDebug & CMBATT_GENERIC_INFO)
                    DbgPrint("CmBattQueryTag - New Tag: (%d)\n", DeviceExtension->Tag);

                /* Reset trip point data */
                DeviceExtension->TripPointOld = 0;
                DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;

                /* Clear AR lock and set new interrupt time */
                InterlockedExchange(&DeviceExtension->ArLockValue, 0);
                DeviceExtension->InterruptTime = KeQueryInterruptTime();
            }
        }
        else
        {
            /* No battery, so no tag */
            DeviceExtension->Tag = 0;
            Status = STATUS_NO_SUCH_DEVICE;
        }
    }

    /* Return the tag and status result */
    *Tag = DeviceExtension->Tag;
    if (CmBattDebug & CMBATT_ACPI_WARNING)
      DbgPrint("CmBattQueryTag: Returning Tag: 0x%x, status 0x%x\n", *Tag, Status);
    return Status;
}

NTSTATUS
NTAPI
CmBattDisableStatusNotify(IN PCMBATT_DEVICE_EXTENSION DeviceExtension)
{
    NTSTATUS Status;
    PAGED_CODE();
    if (CmBattDebug & 0xA) DbgPrint("CmBattDisableStatusNotify\n");

    /* Do we have a trip point */
    if (DeviceExtension->TripPointSet)
    {
        /* Is there a current value set? */
        if (DeviceExtension->TripPointValue)
        {
            /* Reset it back to 0 */
            DeviceExtension->TripPointValue = 0;
            Status = CmBattSetTripPpoint(DeviceExtension, 0);
            if (!NT_SUCCESS(Status))
            {
                /* If it failed, set unknown/invalid value */
                DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;
                if (CmBattDebug & 8)
                    DbgPrint("CmBattDisableStatusNotify: SetTripPoint failed - %x\n", Status);
            }
        }
        else
        {
            /* No trip point set, so this is a successful no-op */
            Status = STATUS_SUCCESS;
        }
    }
    else
    {
        /* Nothing we can do */
        Status = STATUS_OBJECT_NAME_NOT_FOUND;
    }

    /* Return status */
    return Status;
}

NTSTATUS
NTAPI
CmBattSetStatusNotify(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
                      IN ULONG BatteryTag,
                      IN PBATTERY_NOTIFY BatteryNotify)
{
    NTSTATUS Status;
    ACPI_BST_DATA BstData;
    ULONG Capacity, NewTripPoint, TripPoint, DesignVoltage;
    BOOLEAN Charging;
    PAGED_CODE();
    if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
        DbgPrint("CmBattSetStatusNotify: Tag (%d) Target(0x%x)\n",
                 BatteryTag, BatteryNotify->LowCapacity);

    /* Update any ACPI evaluations */
    Status = CmBattVerifyStaticInfo(DeviceExtension, BatteryTag);
    if (!NT_SUCCESS(Status)) return Status;

    /* Trip point not supported, fail */
    if (!DeviceExtension->TripPointSet) return STATUS_OBJECT_NAME_NOT_FOUND;

    /* Are both capacities known? */
    if ((BatteryNotify->HighCapacity == BATTERY_UNKNOWN_CAPACITY) ||
        (BatteryNotify->LowCapacity == BATTERY_UNKNOWN_CAPACITY))
    {
        /* We can't set trip points without these */
        if (CmBattDebug & CMBATT_GENERIC_WARNING)
            DbgPrint("CmBattSetStatusNotify: Failing request because of BATTERY_UNKNOWN_CAPACITY.\n");
        return STATUS_NOT_SUPPORTED;
    }

    /* Is the battery charging? */
    Charging = DeviceExtension->BstData.State & ACPI_BATT_STAT_CHARGING;
    if (Charging)
    {
        /* Then the trip point is when we hit the cap */
        Capacity = BatteryNotify->HighCapacity;
        NewTripPoint = BatteryNotify->HighCapacity;
    }
    else
    {
        /* Otherwise it's when we discharge to the bottom */
        Capacity = BatteryNotify->LowCapacity;
        NewTripPoint = BatteryNotify->LowCapacity;
    }

    /* Do we have data in Amps or Watts? */
    if (DeviceExtension->BifData.PowerUnit == ACPI_BATT_POWER_UNIT_AMPS)
    {
        /* We need the voltage to do the conversion */
        DesignVoltage = DeviceExtension->BifData.DesignVoltage;
        if ((DesignVoltage != BATTERY_UNKNOWN_VOLTAGE) && (DesignVoltage))
        {
            /* Convert from mAh into Ah */
            TripPoint = 1000 * NewTripPoint;
            if (Charging)
            {
                /* Scale the high trip point */
                NewTripPoint = (TripPoint + 500) / DesignVoltage + ((TripPoint + 500) % DesignVoltage != 0);
            }
            else
            {
                /* Scale the low trip point */
                NewTripPoint = (TripPoint - 500) / DesignVoltage - ((TripPoint - 500) % DesignVoltage == 0);
            }
        }
        else
        {
            /* Without knowing the voltage, Amps are not enough data on consumption */
            Status = STATUS_NOT_SUPPORTED;
            if (CmBattDebug & CMBATT_ACPI_WARNING)
                DbgPrint("CmBattSetStatusNotify: Can't calculate BTP, DesignVoltage = 0x%08x\n",
                        DesignVoltage);
        }
    }
    else if (Charging)
    {
        /* Make it trip just one past the charge cap */
        ++NewTripPoint;
    }
    else if (NewTripPoint > 0)
    {
        /* Make it trip just one below the drain cap */
        --NewTripPoint;
    }

    /* Do we actually have a new trip point? */
    if (NewTripPoint == DeviceExtension->TripPointValue)
    {
        /* No, so there is no work to be done */
        if (CmBattDebug & CMBATT_GENERIC_STATUS)
            DbgPrint("CmBattSetStatusNotify: Keeping original setting: %X\n", DeviceExtension->TripPointValue);
        return STATUS_SUCCESS;
    }

    /* Set the trip point with ACPI and check for success */
    DeviceExtension->TripPointValue = NewTripPoint;
    Status = CmBattSetTripPpoint(DeviceExtension, NewTripPoint);
    if (!(NewTripPoint) && (Capacity)) Status = STATUS_NOT_SUPPORTED;
    if (!NT_SUCCESS(Status))
    {
        /* We failed to set the trip point, or there wasn't one settable */
        DeviceExtension->TripPointValue = BATTERY_UNKNOWN_CAPACITY;
        if (CmBattDebug & (CMBATT_GENERIC_WARNING | CMBATT_ACPI_WARNING))
            DbgPrint("CmBattSetStatusNotify: SetTripPoint failed - %x\n", Status);
        return Status;
    }

    /* Read the new BST data to see the latest state */
    Status = CmBattGetBstData(DeviceExtension, &BstData);
    if (!NT_SUCCESS(Status))
    {
        /* We'll return failure to the caller */
        if (CmBattDebug & (CMBATT_GENERIC_WARNING | CMBATT_ACPI_WARNING))
            DbgPrint("CmBattSetStatusNotify: GetBstData - %x\n", Status);
    }
    else if ((Charging) && (BstData.RemainingCapacity >= NewTripPoint))
    {
        /* We are charging and our capacity is past the trip point, so trip now */
        if (CmBattDebug & CMBATT_GENERIC_WARNING)
            DbgPrint("CmBattSetStatusNotify: Trip point already crossed (1): TP = %08x, remaining capacity = %08x\n",
                     NewTripPoint, BstData.RemainingCapacity);
        CmBattNotifyHandler(DeviceExtension, ACPI_BATT_NOTIFY_STATUS);
    }
    else if ((BstData.RemainingCapacity) && (Capacity))
    {
        /* We are discharging, and our capacity is below the trip point, trip now */
        if (CmBattDebug & CMBATT_GENERIC_WARNING)
            DbgPrint("CmBattSetStatusNotify: Trip point already crossed (1): TP = %08x, remaining capacity = %08x\n",
                     NewTripPoint, BstData.RemainingCapacity);
        CmBattNotifyHandler(DeviceExtension, ACPI_BATT_NOTIFY_STATUS);
    }

    /* All should've went well if we got here, unless BST failed... return! */
    if (CmBattDebug & CMBATT_GENERIC_STATUS)
          DbgPrint("CmBattSetStatusNotify: Want %X CurrentCap %X\n",
                    Capacity, DeviceExtension->RemainingCapacity);
    if (CmBattDebug & CMBATT_ACPI_WARNING)
          DbgPrint("CmBattSetStatusNotify: Set to: [%#08lx][%#08lx][%#08lx] Status %lx\n",
                    BatteryNotify->PowerState,
                    BatteryNotify->LowCapacity,
                    BatteryNotify->HighCapacity,
                    Status);
    return Status;
}

NTSTATUS
NTAPI
CmBattGetBatteryStatus(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
                       IN ULONG Tag)
{
    ULONG PsrData = 0;
    NTSTATUS Status;
    ULONG BstState;
    ULONG DesignVoltage, PresentRate, RemainingCapacity;
    PAGED_CODE();
    if (CmBattDebug & CMBATT_GENERIC_INFO)
        DbgPrint("CmBattGetBatteryStatus - CmBatt (%08x) Tag (%d)\n", DeviceExtension, Tag);

    /* Validate ACPI data */
    Status = CmBattVerifyStaticInfo(DeviceExtension, Tag);
    if (!NT_SUCCESS(Status)) return Status;

    /* Check for delayed status notifications */
    if (DeviceExtension->DelayNotification)
    {
        /* Process them now and don't do any other work */
        CmBattNotifyHandler(DeviceExtension, ACPI_BATT_NOTIFY_STATUS);
        return Status;
    }

    /* Get _BST from ACPI */
    Status = CmBattGetBstData(DeviceExtension, &DeviceExtension->BstData);
    if (!NT_SUCCESS(Status))
    {
        /* Fail */
        InterlockedExchange(&DeviceExtension->ArLockValue, 0);
        return Status;
    }

    /* Clear current BST information */
    DeviceExtension->State = 0;
    DeviceExtension->RemainingCapacity = 0;
    DeviceExtension->PresentVoltage = 0;
    DeviceExtension->Rate = 0;

    /* Get battery state */
    BstState = DeviceExtension->BstData.State;

    /* Is the battery both charging and discharging? */
    if ((BstState & ACPI_BATT_STAT_DISCHARG) && (BstState & ACPI_BATT_STAT_CHARGING) &&
        (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_WARNING)))
            DbgPrint("************************ ACPI BIOS BUG ********************\n* "
                     "CmBattGetBatteryStatus: Invalid state: _BST method returned 0x%08x for Battery State.\n"
                     "* One battery cannot be charging and discharging at the same time.\n",
                     BstState);

    /* Is the battery discharging? */
    if (BstState & ACPI_BATT_STAT_DISCHARG)
    {
        /* Set power state and check if it just started discharging now */
        DeviceExtension->State |= BATTERY_DISCHARGING;
        if (!(DeviceExtension->State & ACPI_BATT_STAT_DISCHARG))
        {
            /* Remember the time when the state changed */
            DeviceExtension->InterruptTime = KeQueryInterruptTime();
        }
    }
    else if (BstState & ACPI_BATT_STAT_CHARGING)
    {
        /* Battery is charging, update power state */
        DeviceExtension->State |= (BATTERY_CHARGING | BATTERY_POWER_ON_LINE);
    }

    /* Is the battery in a critical state? */
    if (BstState & ACPI_BATT_STAT_CRITICAL) DeviceExtension->State |= BATTERY_CRITICAL;

    /* Read the voltage data */
    DeviceExtension->PresentVoltage = DeviceExtension->BstData.PresentVoltage;

    /* Check if we have an A/C adapter */
    if (AcAdapterPdo)
    {
        /* Query information on it */
        CmBattGetPsrData(AcAdapterPdo, &PsrData);
    }
    else
    {
        /* Otherwise, check if the battery is charging */
        if (BstState & ACPI_BATT_STAT_CHARGING)
        {
            /* Then we'll assume there's a charger */
            PsrData = 1;
        }
        else
        {
            /* Assume no charger */
            PsrData = 0;
        }
    }

    /* Is there a charger? */
    if (PsrData)
    {
        /* Set the power state flag to reflect this */
        DeviceExtension->State |= BATTERY_POWER_ON_LINE;
        if (CmBattDebug & (CMBATT_GENERIC_INFO | CMBATT_GENERIC_STATUS))
            DbgPrint("CmBattGetBatteryStatus: AC adapter is connected\n");
    }
    else if (CmBattDebug & (CMBATT_GENERIC_INFO | CMBATT_GENERIC_STATUS))
    {
        DbgPrint("CmBattGetBatteryStatus: AC adapter is NOT connected\n");
    }

    /* Get some data we'll need */
    DesignVoltage = DeviceExtension->BifData.DesignVoltage;
    PresentRate = DeviceExtension->BstData.PresentRate;
    RemainingCapacity = DeviceExtension->BstData.RemainingCapacity;

    /* Check if we have battery data in Watts instead of Amps */
    if (DeviceExtension->BifData.PowerUnit == ACPI_BATT_POWER_UNIT_WATTS)
    {
        /* Get the data from the BST */
        DeviceExtension->RemainingCapacity = RemainingCapacity;
        DeviceExtension->Rate = PresentRate;

        /* Check if the rate is invalid */
        if (PresentRate > CM_MAX_VALUE)
        {
            /* Set an unknown rate and don't touch the old value */
            DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
            if ((PresentRate != CM_UNKNOWN_VALUE) && (CmBattDebug & CMBATT_ACPI_WARNING))
            {
                DbgPrint("CmBattGetBatteryStatus - Rate is greater than CM_MAX_VALUE\n");
                DbgPrint("----------------------   PresentRate = 0x%08x\n", PresentRate);
            }
        }
    }
    else if ((DesignVoltage != CM_UNKNOWN_VALUE) && (DesignVoltage))
    {
        /* We have voltage data, what about capacity? */
        if (RemainingCapacity == CM_UNKNOWN_VALUE)
        {
            /* Unable to calculate it */
            DeviceExtension->RemainingCapacity = BATTERY_UNKNOWN_CAPACITY;
            if (CmBattDebug & CMBATT_ACPI_WARNING)
            {
                DbgPrint("CmBattGetBatteryStatus - Can't calculate RemainingCapacity \n");
                DbgPrint("----------------------   RemainingCapacity = CM_UNKNOWN_VALUE\n");
            }
        }
        else
        {
            /* Compute the capacity with the information we have */
            DeviceExtension->RemainingCapacity = (DesignVoltage * RemainingCapacity + 500) / 1000;
        }

        /* Check if we have a rate */
        if (PresentRate != CM_UNKNOWN_VALUE)
        {
            /* Make sure the rate isn't too large */
            if (PresentRate > (-500 / DesignVoltage))
            {
                /* It is, so set unknown state */
                DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
                if (CmBattDebug & CMBATT_ACPI_WARNING)
                {
                    DbgPrint("CmBattGetBatteryStatus - Can't calculate Rate \n");
                    DbgPrint("----------------------   Overflow: PresentRate = 0x%08x\n", PresentRate);
                }
            }

            /* Compute the rate */
            DeviceExtension->Rate = (PresentRate * DesignVoltage + 500) / 1000;
        }
        else
        {
            /* We don't have a rate, so set unknown value */
            DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
            if (CmBattDebug & CMBATT_ACPI_WARNING)
            {
                DbgPrint("CmBattGetBatteryStatus - Can't calculate Rate \n");
                DbgPrint("----------------------   Present Rate = CM_UNKNOWN_VALUE\n");
            }
        }
    }
    else
    {
        /* We have no rate, and no capacity, set unknown values */
        DeviceExtension->Rate = BATTERY_UNKNOWN_RATE;
        DeviceExtension->RemainingCapacity = BATTERY_UNKNOWN_CAPACITY;
        if (CmBattDebug & CMBATT_ACPI_WARNING)
        {
            DbgPrint("CmBattGetBatteryStatus - Can't calculate RemainingCapacity and Rate \n");
            DbgPrint("----------------------   DesignVoltage = 0x%08x\n", DesignVoltage);
        }
    }

    /* Check if we have an unknown rate */
    if (DeviceExtension->Rate == BATTERY_UNKNOWN_RATE)
    {
        /* The battery is discharging but we don't know by how much... this is bad! */
        if ((BstState & ACPI_BATT_STAT_DISCHARG) &&
            (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_WARNING)))
            DbgPrint("CmBattGetBatteryStatus: battery rate is unknown when battery is not charging!\n");
    }
    else if (DeviceExtension->State & BATTERY_DISCHARGING)
    {
        /* The battery is discharging, so treat the rate as a negative rate */
        DeviceExtension->Rate = -(LONG)DeviceExtension->Rate;
    }
    else if (!(DeviceExtension->State & BATTERY_CHARGING) && (DeviceExtension->Rate))
    {
        /* We are not charging, not discharging, but have a rate? Ignore it! */
        if (CmBattDebug & CMBATT_GENERIC_WARNING)
            DbgPrint("CmBattGetBatteryStatus: battery is not charging or discharging, but rate = %x\n",
                     DeviceExtension->Rate);
        DeviceExtension->Rate = 0;
    }

    /* Done */
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
CmBattQueryInformation(IN PCMBATT_DEVICE_EXTENSION FdoExtension,
                       IN ULONG Tag,
                       IN BATTERY_QUERY_INFORMATION_LEVEL InfoLevel,
                       IN OPTIONAL LONG AtRate,
                       IN PVOID Buffer,
                       IN ULONG BufferLength,
                       OUT PULONG ReturnedLength)
{
    NTSTATUS Status;
    PVOID QueryData = NULL;
    ULONG QueryLength = 0;
    ULONG RemainingTime = 0;
    ANSI_STRING TempString;
    UNICODE_STRING TempString2;
    WCHAR InfoBuffer[256];
    WCHAR TempBuffer[256];
    UNICODE_STRING InfoString;
    ULONG RemainingCapacity;
    BATTERY_REPORTING_SCALE BatteryReportingScale[2];
    LONG Rate;
    PAGED_CODE();
    if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
        DbgPrint("CmBattQueryInformation - Tag (%d) Device %d, Informationlevel %d\n",
                 Tag,
                 FdoExtension->DeviceId,
                 InfoLevel);

    /* Check ACPI Data */
    Status = CmBattVerifyStaticInfo(FdoExtension, Tag);
    if (!NT_SUCCESS(Status)) return Status;

    /* Check what caller wants */
    switch (InfoLevel)
    {
        case BatteryInformation:
            /* Just return our static information */
            QueryData = &FdoExtension->BatteryInformation;
            QueryLength = sizeof(BATTERY_INFORMATION);
            break;

        case BatteryGranularityInformation:

            /* Return our static information, we have two scales */
            BatteryReportingScale[0].Granularity = FdoExtension->BatteryCapacityGranularity1;
            BatteryReportingScale[0].Capacity = FdoExtension->BatteryInformation.DefaultAlert1;
            BatteryReportingScale[1].Granularity = FdoExtension->BatteryCapacityGranularity2;
            BatteryReportingScale[1].Capacity = FdoExtension->BatteryInformation.DesignedCapacity;
            QueryData = BatteryReportingScale;
            QueryLength = sizeof(BATTERY_REPORTING_SCALE) * 2;
            break;

        case BatteryEstimatedTime:

            /* Check if it's been more than 2 1/2 minutes since the last change */
            if ((KeQueryInterruptTime() - 150000000) > (FdoExtension->InterruptTime))
            {
                /* Get new battery status */
                CmBattGetBatteryStatus(FdoExtension, FdoExtension->Tag);

                /* If the caller didn't specify a rate, use our static one */
                Rate = AtRate;
                if (!Rate) Rate = FdoExtension->Rate;

                /* If we don't have a valid negative rate, use unknown value */
                if (Rate >= 0) Rate = BATTERY_UNKNOWN_RATE;

                /* Grab the remaining capacity */
                RemainingCapacity = FdoExtension->RemainingCapacity;

                /* See if we don't know one or the other */
                if ((Rate == BATTERY_UNKNOWN_RATE) ||
                    (RemainingCapacity == BATTERY_UNKNOWN_CAPACITY))
                {
                    /* If the battery is discharging, we can't give out a time */
                    if ((FdoExtension->BstData.State & ACPI_BATT_STAT_DISCHARG) &&
                        (CmBattDebug & CMBATT_GENERIC_WARNING))
                            DbgPrint("CmBattQueryInformation: Can't calculate EstimatedTime.\n");

                    /* Check if we don't have a rate and capacity is going down */
                    if ((FdoExtension->Rate == BATTERY_UNKNOWN_RATE) &&
                        (FdoExtension->BstData.State & ACPI_BATT_STAT_DISCHARG))
                    {
                        /* We have to fail, since we lack data */
                        Status = STATUS_INVALID_DEVICE_REQUEST;
                        if (CmBattDebug & CMBATT_GENERIC_WARNING)
                            DbgPrint("----------------------   PresentRate = BATTERY_UNKNOWN_RATE\n");
                    }

                    /* If we don't have capacity, the rate is useless */
                    if (RemainingCapacity == BATTERY_UNKNOWN_CAPACITY)
                    {
                        /* We have to fail the request */
                        Status = STATUS_INVALID_DEVICE_REQUEST;
                        if (CmBattDebug & CMBATT_GENERIC_WARNING)
                            DbgPrint("----------------------   RemainingCapacity = BATTERY_UNKNOWN_CAPACITY\n");
                    }
                }
                else
                {
                    /* We have data, but is it valid? */
                    if (RemainingCapacity > 0x123456)
                    {
                        /* The capacity seems bogus, so don't use it */
                        if (CmBattDebug & CMBATT_ACPI_WARNING)
                            DbgPrint("CmBattQueryInformation: Data Overflow in calculating Remaining Capacity.\n");
                    }
                    else
                    {
                        /* Compute the remaining time in seconds, based on rate */
                        RemainingTime = (RemainingCapacity * 3600) / -Rate;
                    }
                }
            }

            /* Return the remaining time */
            QueryData = &RemainingTime;
            QueryLength = sizeof(ULONG);
            break;

        case BatteryDeviceName:

            /* Build the model number string */
            RtlInitAnsiString(&TempString, FdoExtension->BifData.ModelNumber);

            /* Convert it to Unicode */
            InfoString.Buffer = InfoBuffer;
            InfoString.MaximumLength = sizeof(InfoBuffer);
            Status = RtlAnsiStringToUnicodeString(&InfoString, &TempString, 0);

            /* Return the unicode buffer */
            QueryData = InfoString.Buffer;
            QueryLength = InfoString.Length;
            break;

        case BatteryTemperature:
        case BatteryManufactureDate:

            /* We don't support these */
            Status = STATUS_INVALID_DEVICE_REQUEST;
            break;

        case BatteryManufactureName:

            /* Build the OEM info string */
            RtlInitAnsiString(&TempString, FdoExtension->BifData.OemInfo);

            /* Convert it to Unicode */
            InfoString.Buffer = InfoBuffer;
            InfoString.MaximumLength = sizeof(InfoBuffer);
            Status = RtlAnsiStringToUnicodeString(&InfoString, &TempString, 0);

            /* Return the unicode buffer */
            QueryData = InfoString.Buffer;
            QueryLength = InfoString.Length;
            break;

        case BatteryUniqueID:

            /* Build the serial number string */
            RtlInitAnsiString(&TempString, FdoExtension->BifData.SerialNumber);

            /* Convert it to Unicode */
            InfoString.Buffer = InfoBuffer;
            InfoString.MaximumLength = sizeof(InfoBuffer);
            RtlAnsiStringToUnicodeString(&InfoString, &TempString, 0);

            /* Setup a temporary string for concatenation */
            TempString2.Buffer = TempBuffer;
            TempString2.MaximumLength = sizeof(TempBuffer);

            /* Check if there's an OEM string */
            if (FdoExtension->BifData.OemInfo[0])
            {
                /* Build the OEM info string */
                RtlInitAnsiString(&TempString, FdoExtension->BifData.OemInfo);

                /* Convert it to Unicode and append it */
                RtlAnsiStringToUnicodeString(&TempString2, &TempString, 0);
                RtlAppendUnicodeStringToString(&InfoString, &TempString2);
            }

            /* Build the model number string */
            RtlInitAnsiString(&TempString, FdoExtension->BifData.ModelNumber);

            /* Convert it to Unicode and append it */
            RtlAnsiStringToUnicodeString(&TempString2, &TempString, 0);
            RtlAppendUnicodeStringToString(&InfoString, &TempString2);

            /* Return the final appended string */
            QueryData = InfoString.Buffer;
            QueryLength = InfoString.Length;
            break;

        default:

            /* Everything else is unknown */
            Status = STATUS_INVALID_PARAMETER;
            break;
    }

    /* Return the required length and check if the caller supplied enough */
    *ReturnedLength = QueryLength;
    if (BufferLength < QueryLength) Status = STATUS_BUFFER_TOO_SMALL;

    /* Copy the data if there's enough space and it exists */
    if ((NT_SUCCESS(Status)) && (QueryData)) RtlCopyMemory(Buffer, QueryData, QueryLength);

    /* Return function result */
    return Status;
}

NTSTATUS
NTAPI
CmBattQueryStatus(IN PCMBATT_DEVICE_EXTENSION DeviceExtension,
                  IN ULONG Tag,
                  IN PBATTERY_STATUS BatteryStatus)
{
    NTSTATUS Status;
    PAGED_CODE();
    if (CmBattDebug & (CMBATT_ACPI_WARNING | CMBATT_GENERIC_INFO))
        DbgPrint("CmBattQueryStatus - Tag (%d) Device %x\n", Tag, DeviceExtension->DeviceId);

    /* Query ACPI information */
    Status = CmBattGetBatteryStatus(DeviceExtension, Tag);
    if (NT_SUCCESS(Status))
    {
        BatteryStatus->PowerState = DeviceExtension->State;
        BatteryStatus->Capacity = DeviceExtension->RemainingCapacity;
        BatteryStatus->Voltage = DeviceExtension->PresentVoltage;
        BatteryStatus->Rate = DeviceExtension->Rate;
    }

    /* Return status */
    if (CmBattDebug & (CMBATT_GENERIC_INFO))
        DbgPrint("CmBattQueryStatus: Returning [%#08lx][%#08lx][%#08lx][%#08lx]\n",
                 BatteryStatus->PowerState,
                 BatteryStatus->Capacity,
                 BatteryStatus->Voltage,
                 BatteryStatus->Rate);
    return Status;
}

NTSTATUS
NTAPI
DriverEntry(IN PDRIVER_OBJECT DriverObject,
            IN PUNICODE_STRING RegistryPath)
{
    NTSTATUS Status;
    PDRIVER_EXTENSION DriverExtension;
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING CallbackName;

    /* Allocate registry path */
    GlobalRegistryPath.MaximumLength = RegistryPath->Length + sizeof(UNICODE_NULL);
    GlobalRegistryPath.Length = RegistryPath->Length;
    GlobalRegistryPath.Buffer = ExAllocatePoolWithTag(PagedPool,
                                                      GlobalRegistryPath.MaximumLength,
                                                      'MtaB');
    if (!GlobalRegistryPath.Buffer)
    {
        /* Fail if we're out of memory this early */
        if (CmBattDebug & CMBATT_GENERIC_WARNING)
            DbgPrint("CmBatt: Couldn't allocate pool for registry path.");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* Buffer allocated, copy the string */
    RtlCopyUnicodeString(&GlobalRegistryPath, RegistryPath);
    if (CmBattDebug & CMBATT_GENERIC_INFO)
        DbgPrint("CmBatt DriverEntry - Obj (%08x) Path \"%wZ\"\n",
                 DriverObject,
                 RegistryPath);

    /* Setup the major dispatchers */
    DriverObject->MajorFunction[IRP_MJ_CREATE] = CmBattOpenClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = CmBattOpenClose;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CmBattIoctl;
    DriverObject->MajorFunction[IRP_MJ_POWER] = CmBattPowerDispatch;
    DriverObject->MajorFunction[IRP_MJ_PNP] = CmBattPnpDispatch;
    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = CmBattSystemControl;

    /* And the unload routine */
    DriverObject->DriverUnload = CmBattUnload;

    /* And the add device routine */
    DriverExtension = DriverObject->DriverExtension;
    DriverExtension->AddDevice = CmBattAddDevice;

    /* Create a power callback */
    RtlInitUnicodeString(&CallbackName, L"\\Callback\\PowerState");
    InitializeObjectAttributes(&ObjectAttributes,
                               &CallbackName,
                               OBJ_KERNEL_HANDLE,
                               NULL,
                               NULL);
    Status = ExCreateCallback(&CmBattPowerCallBackObject, &ObjectAttributes, 0, TRUE);
    if (!NT_SUCCESS(Status))
    {
        /* No callback, fail */
        CmBattPowerCallBackObject = 0;
        if (CmBattDebug & CMBATT_GENERIC_WARNING)
            DbgPrint("CmBattRegisterPowerCallBack: failed status=0x%08x\n", Status);
    }
    else
    {
        /* Register the power callback now */
        CmBattPowerCallBackRegistration = ExRegisterCallback(CmBattPowerCallBackObject,
                                                             (PVOID)CmBattPowerCallBack,
                                                             DriverObject);
        if (CmBattPowerCallBackRegistration)
        {
            /* Last thing: setup our DPC and timer for battery wake */
            KeInitializeDpc(&CmBattWakeDpcObject, (PVOID)CmBattWakeDpc, DriverObject);
            KeInitializeTimer(&CmBattWakeDpcTimerObject);
        }
        else
        {
            ObDereferenceObject(CmBattPowerCallBackObject);
            if (CmBattDebug & CMBATT_GENERIC_WARNING)
                DbgPrint("CmBattRegisterPowerCallBack: ExRegisterCallback failed.\n");
        }

        /* All good */
        Status = STATUS_SUCCESS;
    }

    /* Return failure or success */
    return Status;
}

/* EOF */
