mirror of
https://github.com/ApfelTeeSaft/reactos.git
synced 2026-09-02 12:23:31 +00:00
CORE-20343
The 3-second timeout FIXME (instead of waiting indefinitely), made in the
COMPBATT worker thread for the `IOCTL_BATTERY_QUERY_STATUS` case, was done
because _*our*_ BATTC handler expects the batteries to always support the
BTP (Battery Trip Point) feature for signaling a change of battery status,
but in the cases where it isn't supported, any waits it tried for the
battery to notify about a status change would never happen.
Furthermore, following commit 3a6e0d4b65, the `SetStatusNotify()` call
(_which always fails if the battery doesn't support the BTP feature_)
would now always exit the `IOCTL_BATTERY_QUERY_STATUS` handling without
any waiting nor battery polling [^1], and this would cause the COMPBATT
worker thread to busy-poll again forever.
The timeout FIXME is now moved to BATTC, instead of COMPBATT, since the
actual fixes should be in BATTC. In particular, it should queue all the
query IOCTLs and then serve them, either using the StatusNotify (BTP)
functionality if the battery supports it, or if not, do a very-slow
battery polling.
I've also increased the timeout a little bit more (5 seconds and not 3).
[^1]: Per the ACPI specification, it is expected that the operating system
performs battery polling if the battery doesn't support BTP, see:
https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/10_Power_Source_and_Power_Meter_Devices/Power_Source_and_Power_Meter_Devices.html#btp-battery-trip-point
1789 lines
71 KiB
C
1789 lines
71 KiB
C
/*
|
|
* PROJECT: ReactOS Composite Battery Driver
|
|
* LICENSE: MIT (https://spdx.org/licenses/MIT)
|
|
* PURPOSE: Main Initialization Code and IRP Handling
|
|
* COPYRIGHT: Copyright 2010 ReactOS Portable Systems Group <[email protected]>
|
|
* Copyright 2024 George Bișoc <[email protected]>
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#include "compbatt.h"
|
|
|
|
#include <debug.h>
|
|
|
|
/* GLOBALS ********************************************************************/
|
|
|
|
ULONG CompBattDebug;
|
|
|
|
/* FUNCTIONS ******************************************************************/
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattOpenClose(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp)
|
|
{
|
|
PAGED_CODE();
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING OpenClose\n");
|
|
|
|
/* Complete the IRP with success */
|
|
Irp->IoStatus.Status = STATUS_SUCCESS;
|
|
Irp->IoStatus.Information = 0;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
|
|
/* Return success */
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: Exiting OpenClose\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattSystemControl(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp)
|
|
{
|
|
PCOMPBATT_DEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING System Control\n");
|
|
|
|
/* Are we attached yet? */
|
|
if (DeviceExtension->AttachedDevice)
|
|
{
|
|
/* Send it up the stack */
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
Status = IoCallDriver(DeviceExtension->AttachedDevice, Irp);
|
|
}
|
|
else
|
|
{
|
|
/* We don't support WMI */
|
|
Status = STATUS_NOT_SUPPORTED;
|
|
Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
|
|
IoCompleteRequest(Irp, IO_NO_INCREMENT);
|
|
}
|
|
|
|
/* Return status */
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Queues a work item thread worker which is bound to the individual
|
|
* CM (Control Method) ACPI battery to handle the IRP.
|
|
*
|
|
* @param[in] DeviceObject
|
|
* A pointer to a device object, this parameter is unused.
|
|
*
|
|
* @param[in] Irp
|
|
* A pointer to an I/O request packet. It is used to gather the I/O stack
|
|
* location which contains the data of the individual battery.
|
|
*
|
|
* @param[in] Context
|
|
* An arbitrary pointer that points to context data, this parameter
|
|
* is unused.
|
|
*
|
|
* @return
|
|
* Returns STATUS_MORE_PROCESSING_REQUIRED to indicate the I/O request
|
|
* is still in action, therefore the IRP is not freed.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattMonitorIrpComplete(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp,
|
|
_In_ PVOID Context)
|
|
{
|
|
PIO_STACK_LOCATION IoStackLocation;
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
|
|
/* We do not care about the device object */
|
|
UNREFERENCED_PARAMETER(DeviceObject);
|
|
|
|
/* Grab the composite battery data from the I/O stack packet */
|
|
IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
|
|
BatteryData = IoStackLocation->Parameters.Others.Argument2;
|
|
|
|
/* Request the IRP complete worker to do the deed */
|
|
ExQueueWorkItem(&BatteryData->WorkItem, DelayedWorkQueue);
|
|
return STATUS_MORE_PROCESSING_REQUIRED;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* The brains of the battery IRP worker. It monitors the state of the
|
|
* IRP as well as sends the IRP down the device stack to gather battery
|
|
* related data, such tag and status. It also serves as the I/O
|
|
* completion routine of which it elaborates the gathered data.
|
|
*
|
|
* @param[in] BatteryData
|
|
* A pointer to battery data of an individual battery that contains
|
|
* the IRP to be send down the device stack.
|
|
*/
|
|
VOID
|
|
NTAPI
|
|
CompBattMonitorIrpCompleteWorker(
|
|
_In_ PCOMPBATT_BATTERY_DATA BatteryData)
|
|
{
|
|
NTSTATUS Status;
|
|
PIRP Irp;
|
|
UCHAR Mode;
|
|
ULONG PrevPowerState;
|
|
PDEVICE_OBJECT DeviceObject;
|
|
BATTERY_STATUS BatteryStatus;
|
|
PIO_STACK_LOCATION IoStackLocation;
|
|
PCOMPBATT_DEVICE_EXTENSION DeviceExtension;
|
|
|
|
/* Cache the necessary battery data */
|
|
Irp = BatteryData->Irp;
|
|
DeviceObject = BatteryData->DeviceObject;
|
|
IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
|
|
DeviceExtension = IoStackLocation->Parameters.Others.Argument1;
|
|
|
|
/* Switch to the next stack as we have to setup the control function data there */
|
|
IoStackLocation = IoGetNextIrpStackLocation(Irp);
|
|
|
|
/* Has the I/O composite battery request succeeded? */
|
|
Status = Irp->IoStatus.Status;
|
|
if (!NT_SUCCESS(Status) && Status != STATUS_CANCELLED)
|
|
{
|
|
/*
|
|
* This battery is being removed from the composite, perform
|
|
* cleanups and do not inquire I/O requests again on this battery.
|
|
*/
|
|
if (Status == STATUS_DEVICE_REMOVED)
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Battery (0x%p) is being removed from composite battery\n", BatteryData);
|
|
|
|
IoFreeIrp(Irp);
|
|
CompBattRemoveBattery(&BatteryData->BatteryName, DeviceExtension);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This is the first time a battery is being added into the composite
|
|
* (we understand that if Status was STATUS_DEVICE_NOT_CONNECTED).
|
|
* We must invalidate the composite tag and request a recalculation
|
|
* of the battery tag.
|
|
*/
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Battery arrived for first time or disappeared (Status 0x%08lx)\n", Status);
|
|
|
|
BatteryData->Tag = BATTERY_TAG_INVALID;
|
|
|
|
/*
|
|
* Invalidate the last read status interrupt time as well since the last
|
|
* battery status data no longer applies. Same for the composite battery
|
|
* as well.
|
|
*/
|
|
BatteryData->InterruptTime = 0;
|
|
DeviceExtension->InterruptTime = 0;
|
|
|
|
/* Notify Battery Class the battery status incurs in a change */
|
|
BatteryClassStatusNotify(DeviceExtension->ClassData);
|
|
|
|
/* Setup the necessary I/O data to query the battery tag */
|
|
IoStackLocation->Parameters.DeviceIoControl.IoControlCode = IOCTL_BATTERY_QUERY_TAG;
|
|
IoStackLocation->Parameters.DeviceIoControl.InputBufferLength = sizeof(ULONG);
|
|
IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength = sizeof(ULONG);
|
|
BatteryData->Mode = COMPBATT_QUERY_TAG;
|
|
BatteryData->WorkerBuffer.WorkerTag = 0xFFFFFFFF;
|
|
|
|
/* Dispatch our request now to the battery's driver */
|
|
goto DispatchRequest;
|
|
}
|
|
|
|
/* Our I/O request has been completed successfully, check what did we get */
|
|
Mode = BatteryData->Mode;
|
|
switch (Mode)
|
|
{
|
|
case COMPBATT_QUERY_TAG:
|
|
{
|
|
/*
|
|
* This battery has just gotten a tag, acknowledge the composite battery
|
|
* about that so it can recalculate its own composite tag.
|
|
*/
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Battery (Device 0x%p) has a tag of %lu\n", DeviceObject, BatteryData->WorkerBuffer.WorkerTag);
|
|
|
|
/* Ensure the battery tag is not bogus, getting a tag of 0 is illegal */
|
|
ASSERT(BatteryData->WorkerBuffer.WorkerTag != 0);
|
|
|
|
/* Assign the battery tag */
|
|
BatteryData->Tag = BatteryData->WorkerBuffer.WorkerTag;
|
|
BatteryData->Flags |= COMPBATT_TAG_ASSIGNED;
|
|
|
|
/* Punt the composite battery flags, as the previous cached data no longer applies */
|
|
DeviceExtension->Flags = 0;
|
|
|
|
/* Notify the Battery Class driver this battery has got a tag */
|
|
BatteryClassStatusNotify(DeviceExtension->ClassData);
|
|
break;
|
|
}
|
|
|
|
case COMPBATT_READ_STATUS:
|
|
{
|
|
/*
|
|
* Read the battery status only if the IRP has not been cancelled,
|
|
* otherwise the request must be re-issued again. This typically
|
|
* happens if the wait values are in conflict which it might
|
|
* end up in inconsistent battery status results.
|
|
*/
|
|
if (Status != STATUS_CANCELLED && !Irp->Cancel)
|
|
{
|
|
/*
|
|
* If we reach here then the battery has entered into a change of
|
|
* power state or its charge capacity has changed.
|
|
*/
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Battery state (Device 0x%p) has changed\n", DeviceObject);
|
|
|
|
/* Copy the battery status of this battery */
|
|
RtlCopyMemory(&BatteryData->BatteryStatus,
|
|
&BatteryData->WorkerBuffer.WorkerStatus,
|
|
sizeof(BatteryData->BatteryStatus));
|
|
|
|
/* Update the interrupt time as this is the most recent read of the battery status */
|
|
BatteryData->InterruptTime = KeQueryInterruptTime();
|
|
|
|
/*
|
|
* Ensure we have not gotten unknown capacities while we waited for new
|
|
* battery status. The battery might have malfunctioned or something.
|
|
*/
|
|
if (BatteryData->WorkerBuffer.WorkerStatus.Capacity == BATTERY_UNKNOWN_CAPACITY)
|
|
{
|
|
/* We do not know the capacity of this battery, default the low and high capacities */
|
|
BatteryData->WaitStatus.LowCapacity = BATTERY_UNKNOWN_CAPACITY;
|
|
BatteryData->WaitStatus.HighCapacity = BATTERY_UNKNOWN_CAPACITY;
|
|
}
|
|
else
|
|
{
|
|
/* We know the capacity, adjust the low and high capacities accordingly */
|
|
if (BatteryData->WaitStatus.LowCapacity >
|
|
BatteryData->WorkerBuffer.WorkerStatus.Capacity)
|
|
{
|
|
BatteryData->WaitStatus.LowCapacity = BatteryData->WorkerBuffer.WorkerStatus.Capacity;
|
|
}
|
|
|
|
if (BatteryData->WaitStatus.HighCapacity <
|
|
BatteryData->WorkerBuffer.WorkerStatus.Capacity)
|
|
{
|
|
BatteryData->WaitStatus.HighCapacity = BatteryData->WorkerBuffer.WorkerStatus.Capacity;
|
|
}
|
|
}
|
|
|
|
/* Copy the current last read power state for the next wait */
|
|
BatteryData->WaitStatus.PowerState = BatteryData->WorkerBuffer.WorkerStatus.PowerState;
|
|
|
|
/*
|
|
* Cache the previous power state of the composite battery and invalidate
|
|
* the last computed battery status interrupt time. This is because,
|
|
* logically, this specific battery incurred in a state change therefore
|
|
* the previous composite status is no longer consistent.
|
|
*/
|
|
PrevPowerState = DeviceExtension->BatteryStatus.PowerState;
|
|
DeviceExtension->InterruptTime = 0;
|
|
|
|
/* Compute a new battery status for the composite battery */
|
|
Status = CompBattQueryStatus(DeviceExtension,
|
|
DeviceExtension->Tag,
|
|
&BatteryStatus);
|
|
|
|
/* Print out the current battery status of the composite to the debugger */
|
|
if ((CompBattDebug & COMPBATT_DEBUG_INFO) && NT_SUCCESS(Status))
|
|
DbgPrint("CompBatt: Latest composite battery status\n"
|
|
" PowerState -> 0x%lx\n"
|
|
" Capacity -> %u\n"
|
|
" Voltage -> %u\n"
|
|
" Rate -> %d\n",
|
|
BatteryStatus.PowerState,
|
|
BatteryStatus.Capacity,
|
|
BatteryStatus.Voltage,
|
|
BatteryStatus.Rate);
|
|
|
|
/*
|
|
* Now determine whether should we notify the Battery Class driver due to
|
|
* changes in power state settings in the composite battery. This could
|
|
* happen in two following conditions:
|
|
*
|
|
* 1. The status notify flag was set for the respective power notification
|
|
* settings, and the composite battery incurred in a change of such
|
|
* settings. In this case we have to probe the current settings that
|
|
* they have changed.
|
|
*
|
|
* 2. The status notify flag was not set, therefore we do not know the
|
|
* exact configuration of the notification settings. We only care that
|
|
* the power state has changed at this point.
|
|
*
|
|
* Why do we have to do this is because we have to warn the Battery Class
|
|
* about the data that has changed.
|
|
*/
|
|
if (!(DeviceExtension->Flags & COMPBATT_STATUS_NOTIFY_SET))
|
|
{
|
|
if (PrevPowerState != DeviceExtension->BatteryStatus.PowerState)
|
|
{
|
|
/* The previous power state is no longer valid, notify Battery Class */
|
|
BatteryClassStatusNotify(DeviceExtension->ClassData);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Unlike the condition above, we check for power state change against
|
|
* the current notify wait set since the notify set flag bit is assigned.
|
|
*/
|
|
if (DeviceExtension->WaitNotifyStatus.PowerState != DeviceExtension->BatteryStatus.PowerState ||
|
|
DeviceExtension->WaitNotifyStatus.LowCapacity > DeviceExtension->BatteryStatus.Capacity ||
|
|
DeviceExtension->WaitNotifyStatus.HighCapacity < DeviceExtension->BatteryStatus.Capacity)
|
|
{
|
|
/* The following configuration settings have changed, notify Battery Class */
|
|
BatteryClassStatusNotify(DeviceExtension->ClassData);
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
ASSERTMSG("CompBatt: BAD!!! WE SHOULD NOT BE HERE!\n", FALSE);
|
|
UNREACHABLE;
|
|
}
|
|
}
|
|
|
|
/* Setup the necessary data to read battery status */
|
|
BatteryData->WaitStatus.BatteryTag = BatteryData->Tag;
|
|
BatteryData->WaitStatus.Timeout = -1;
|
|
|
|
RtlCopyMemory(&BatteryData->WorkerBuffer.WorkerWaitStatus,
|
|
&BatteryData->WaitStatus,
|
|
sizeof(BatteryData->WaitStatus));
|
|
|
|
IoStackLocation->Parameters.DeviceIoControl.IoControlCode = IOCTL_BATTERY_QUERY_STATUS;
|
|
IoStackLocation->Parameters.DeviceIoControl.InputBufferLength = sizeof(BatteryData->WorkerBuffer.WorkerWaitStatus);
|
|
IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength = sizeof(BatteryData->WorkerBuffer.WorkerStatus);
|
|
BatteryData->Mode = COMPBATT_READ_STATUS;
|
|
|
|
DispatchRequest:
|
|
/* Setup the system buffer to that of the battery data which it will hold the returned data */
|
|
IoStackLocation->MajorFunction = IRP_MJ_DEVICE_CONTROL;
|
|
Irp->AssociatedIrp.SystemBuffer = &BatteryData->WorkerBuffer;
|
|
Irp->Cancel = FALSE;
|
|
Irp->PendingReturned = FALSE;
|
|
|
|
/* Setup the worker completion routine which it will invoke the worker later on */
|
|
IoSetCompletionRoutine(Irp,
|
|
CompBattMonitorIrpComplete,
|
|
NULL,
|
|
TRUE,
|
|
TRUE,
|
|
TRUE);
|
|
|
|
/* Dispatch the I/O request now */
|
|
IoCallDriver(DeviceObject, Irp);
|
|
}
|
|
|
|
VOID
|
|
NTAPI
|
|
CompBattRecalculateTag(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
ULONG Tag;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING CompBattRecalculateTag\n");
|
|
|
|
/* Loop the battery list */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
NextEntry = ListHead->Flink;
|
|
while (NextEntry != ListHead)
|
|
{
|
|
/* Get the battery information and check if it has a tag */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (BatteryData->Flags & COMPBATT_TAG_ASSIGNED)
|
|
{
|
|
/* Generate the next tag and exit */
|
|
Tag = DeviceExtension->NextTag;
|
|
DeviceExtension->Flags |= COMPBATT_TAG_ASSIGNED;
|
|
DeviceExtension->Tag = Tag;
|
|
DeviceExtension->NextTag = Tag + 1;
|
|
break;
|
|
}
|
|
|
|
/* No tag for this device extension, clear it */
|
|
DeviceExtension->Tag = BATTERY_TAG_INVALID;
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
/* We're done */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING CompBattRecalculateTag\n");
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattIoctl(
|
|
_In_ PDEVICE_OBJECT DeviceObject,
|
|
_In_ PIRP Irp)
|
|
{
|
|
PCOMPBATT_DEVICE_EXTENSION DeviceExtension = DeviceObject->DeviceExtension;
|
|
NTSTATUS Status;
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING Ioctl\n");
|
|
|
|
/* Let the class driver handle it */
|
|
Status = BatteryClassIoctl(DeviceExtension->ClassData, Irp);
|
|
if (Status == STATUS_NOT_SUPPORTED)
|
|
{
|
|
/* It failed, try the next driver up the stack */
|
|
Irp->IoStatus.Status = Status;
|
|
IoSkipCurrentIrpStackLocation(Irp);
|
|
Status = IoCallDriver(DeviceExtension->AttachedDevice, Irp);
|
|
}
|
|
|
|
/* Return status */
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING Ioctl\n");
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattQueryTag(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension,
|
|
_Out_ PULONG Tag)
|
|
{
|
|
NTSTATUS Status;
|
|
PAGED_CODE();
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING QueryTag\n");
|
|
|
|
/* Was a tag assigned? */
|
|
if (!(DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED))
|
|
{
|
|
/* Assign one */
|
|
CompBattRecalculateTag(DeviceExtension);
|
|
}
|
|
|
|
/* Do we have a tag now? */
|
|
if ((DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED) && DeviceExtension->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
/* Return the tag */
|
|
*Tag = DeviceExtension->Tag;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
/* No tag */
|
|
*Tag = BATTERY_TAG_INVALID;
|
|
Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
/* Return status */
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING QueryTag\n");
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattDisableStatusNotify(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING DisableStatusNotify\n");
|
|
|
|
/* Loop the battery list */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
NextEntry = ListHead->Flink;
|
|
while (NextEntry != ListHead)
|
|
{
|
|
/* Get the battery information and clear capacity data */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
BatteryData->WaitStatus.LowCapacity = 0;
|
|
BatteryData->WaitStatus.HighCapacity = 0x7FFFFFFF;
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
/* Done */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING DisableStatusNotify\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Calculates the total discharging/charging rate flow of each individual
|
|
* battery linked with the composite battery and determines whether at
|
|
* least one battery is behaving improperly.
|
|
*
|
|
* @param[in] DeviceExtension
|
|
* A pointer to a device extension which describes the composite battery
|
|
* itself. It is used to gather each connected battery in the list with
|
|
* the composite battery.
|
|
*
|
|
* @param[out] TotalRate
|
|
* A pointer returned to caller that describes the total accumulated
|
|
* rate flow of all batteries.
|
|
*
|
|
* @param[out] BatteriesCount
|
|
* A pointer returned to caller that describes the batteries present.
|
|
*
|
|
* @return
|
|
* Returns TRUE if at least one battery is behaving improperly, FALSE
|
|
* otherwise. This is determined by the fact if a battery has a negative
|
|
* rate but is charging, or if it has a positive rate but is discharging.
|
|
*/
|
|
static
|
|
BOOLEAN
|
|
CompBattCalculateTotalRateAndLinkedBatteries(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension,
|
|
_Out_ PULONG TotalRate,
|
|
_Out_ PULONG BatteriesCount)
|
|
{
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
BOOLEAN BadBattery = FALSE;
|
|
ULONG LinkedBatteries = 0;
|
|
ULONG BadBatteriesCount = 0;
|
|
ULONG ComputedRate = 0;
|
|
|
|
/* Loop over the linked batteries and sum up the total capacity rate */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
for (NextEntry = ListHead->Flink;
|
|
NextEntry != ListHead;
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
/* Acquire the remove lock so this battery does not disappear under us */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (!NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
continue;
|
|
|
|
/*
|
|
* Ensure this battery has a valid tag and that its rate capacity
|
|
* is not unknown. Reject unknown rates when calculating the total rate.
|
|
*/
|
|
if ((BatteryData->Tag != BATTERY_TAG_INVALID) &&
|
|
(BatteryData->BatteryStatus.Rate != BATTERY_UNKNOWN_RATE))
|
|
{
|
|
/*
|
|
* Now the ultimate judgement for this battery is to determine
|
|
* if the battery behaves optimally based on its current power
|
|
* state it is and the rate flow of the battery.
|
|
*
|
|
* If the rate flow is positive the battery is receiving power
|
|
* which increases the chemical potential energy as electrons
|
|
* move around, THIS MEANS the battery is CHARGING. If the rate
|
|
* flow is negative the battery cells are producing way less
|
|
* electrical energy, thus the battery is DISCHARGING.
|
|
*
|
|
* A consistent battery is a battery of which power state matches
|
|
* the rate flow. If that were the case, then we have found a bad
|
|
* battery. The worst case is that a battery is physically damanged.
|
|
*/
|
|
if ((BatteryData->BatteryStatus.PowerState & BATTERY_DISCHARGING) &&
|
|
(BatteryData->BatteryStatus.Rate >= 0))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: The battery is discharging but in reality it is charging... (Rate %d)\n",
|
|
BatteryData->BatteryStatus.Rate);
|
|
|
|
BadBattery = TRUE;
|
|
BadBatteriesCount++;
|
|
}
|
|
|
|
if ((BatteryData->BatteryStatus.PowerState & BATTERY_CHARGING) &&
|
|
(BatteryData->BatteryStatus.Rate <= 0))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: The battery is charging but in reality it is discharging... (Rate %d)\n",
|
|
BatteryData->BatteryStatus.Rate);
|
|
|
|
BadBattery = TRUE;
|
|
BadBatteriesCount++;
|
|
}
|
|
|
|
if (((BatteryData->BatteryStatus.PowerState & (BATTERY_CHARGING | BATTERY_DISCHARGING)) == 0) &&
|
|
(BatteryData->BatteryStatus.Rate != 0))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: The battery is neither charging or discharging but has a contradicting rate... (Rate %d)\n",
|
|
BatteryData->BatteryStatus.Rate);
|
|
|
|
BadBattery = TRUE;
|
|
BadBatteriesCount++;
|
|
}
|
|
|
|
/*
|
|
* Sum up the rate of this battery to make up the total, even if that means
|
|
* the battery may have incosistent rate. This is because it is still a linked
|
|
* battery to the composite battery and it is used to power up the system nonetheless.
|
|
*/
|
|
ComputedRate += BatteryData->BatteryStatus.Rate;
|
|
}
|
|
|
|
/* We are done with this individual battery */
|
|
LinkedBatteries++;
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Release the lock as we are no longer poking through the batteries list */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Print out the total count of bad batteries we have found */
|
|
if (BadBatteriesCount > 0)
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: %lu bad batteries have been found!\n", BadBatteriesCount);
|
|
}
|
|
|
|
*TotalRate = ComputedRate;
|
|
*BatteriesCount = LinkedBatteries;
|
|
return BadBattery;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Sets a new configuration battery wait status settings of each battery.
|
|
* The purpose of this is so that the composite battery gets notified
|
|
* of new battery status as if it was a single battery.
|
|
*
|
|
* @param[in] DeviceExtension
|
|
* A pointer to a device extension which describes the composite battery
|
|
* itself. It is used to gather each connected battery in the list with
|
|
* the composite battery.
|
|
*
|
|
* @param[in] BatteryTag
|
|
* A battery tag supplied by the caller. This is typically the tag of
|
|
* the composite battery which is used to check against the cached tag
|
|
* of the composite battery if it has changed or not.
|
|
*
|
|
* @param[in] BatteryNotify
|
|
* A pointer to a structure filled with battery notification settings,
|
|
* supplied by the caller. It is used as the new values for the
|
|
* configuration wait settings.
|
|
*
|
|
* @return
|
|
* Returns STATUS_NO_SUCH_DEVICE if the supplied battery tag does not match
|
|
* with that of the cached composite battery's tag or if the composite
|
|
* battery currently does not have a tag assigned. Otherwise STATUS_SUCCESS
|
|
* is returned.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattSetStatusNotify(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension,
|
|
_In_ ULONG BatteryTag,
|
|
_In_ PBATTERY_NOTIFY BatteryNotify)
|
|
{
|
|
NTSTATUS Status;
|
|
BOOLEAN BadBattery;
|
|
ULONG TotalRate;
|
|
ULONG BatteriesCount;
|
|
ULONG HighestCapacity;
|
|
ULONG LowCapDifference, HighCapDifference, LowDelta, HighDelta;
|
|
BATTERY_STATUS BatteryStatus;
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
|
|
/*
|
|
* The caller wants to set new status notification settings but the composite
|
|
* battery does not have a valid tag assigned, or the tag does not actually match.
|
|
*/
|
|
if (!(DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED) ||
|
|
(DeviceExtension->Tag != BatteryTag))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Composite battery tag not assigned or not matching (Tag -> %lu, Composite Tag -> %lu)\n",
|
|
BatteryTag, DeviceExtension->Tag);
|
|
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
/*
|
|
* Before we are setting up new status wait notification points we need to
|
|
* refresh the composite status so that we get to know what values should be
|
|
* set for the current notification wait status.
|
|
*/
|
|
Status = CompBattQueryStatus(DeviceExtension,
|
|
BatteryTag,
|
|
&BatteryStatus);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_ERR)
|
|
DbgPrint("CompBatt: Failed to refresh composite battery's status (Status 0x%08lx)\n", Status);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/* Print out battery status data that has been polled */
|
|
if (CompBattDebug & COMPBATT_DEBUG_INFO)
|
|
DbgPrint("CompBatt: Latest composite battery status (when setting notify status)\n"
|
|
" PowerState -> 0x%lx\n"
|
|
" Capacity -> %u\n"
|
|
" Voltage -> %u\n"
|
|
" Rate -> %d\n",
|
|
BatteryStatus.PowerState,
|
|
BatteryStatus.Capacity,
|
|
BatteryStatus.Voltage,
|
|
BatteryStatus.Rate);
|
|
|
|
/* Calculate the high and low capacity differences based on the real summed capacity of the composite */
|
|
LowCapDifference = DeviceExtension->BatteryStatus.Capacity - BatteryNotify->LowCapacity;
|
|
HighCapDifference = BatteryNotify->HighCapacity - DeviceExtension->BatteryStatus.Capacity;
|
|
|
|
/* Cache the notification parameters provided for later usage when polling for battery status */
|
|
DeviceExtension->WaitNotifyStatus.PowerState = BatteryNotify->PowerState;
|
|
DeviceExtension->WaitNotifyStatus.LowCapacity = BatteryNotify->LowCapacity;
|
|
DeviceExtension->WaitNotifyStatus.HighCapacity = BatteryNotify->HighCapacity;
|
|
|
|
/* Toggle the valid notify flag as these are the newer notification settings */
|
|
DeviceExtension->Flags |= COMPBATT_STATUS_NOTIFY_SET;
|
|
|
|
/*
|
|
* Get the number of currently linked batteries to composite and total rate,
|
|
* we will use these counters later to determine the wait values for each
|
|
* individual battery.
|
|
*/
|
|
BadBattery = CompBattCalculateTotalRateAndLinkedBatteries(DeviceExtension,
|
|
&TotalRate,
|
|
&BatteriesCount);
|
|
|
|
/*
|
|
* Of course we have to be sure that we have at least one battery linked
|
|
* with the composite battery at this time of getting invoked to set new
|
|
* notification wait settings.
|
|
*/
|
|
ASSERT(BatteriesCount != 0);
|
|
|
|
/* Walk over the linked batteries list and set up new wait configuration settings */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
for (NextEntry = ListHead->Flink;
|
|
NextEntry != ListHead;
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
/* Acquire the remove lock so this battery does not disappear under us */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (!NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
continue;
|
|
|
|
/* Now release the device lock since the battery can't go away */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Make sure this battery has a tag before setting new wait values */
|
|
if (BatteryData->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
/*
|
|
* And also make sure this battery does not have an unknown
|
|
* capacity, we cannot set up new configuration wait settings
|
|
* based on that. Default the low and high wait capacities.
|
|
*/
|
|
if (BatteryData->BatteryStatus.Capacity != BATTERY_UNKNOWN_CAPACITY)
|
|
{
|
|
/*
|
|
* Calculate the low capacity wait setting. If at least one
|
|
* bad battery was found while we computed the total composite
|
|
* rate, then divide the difference between the total batteries.
|
|
* Otherwise compute the battery deltas of the composite based
|
|
* on total summed capacity rate. Otherwise if the total rate
|
|
* is 0, then the real wait low and high capacities will be on
|
|
* par with the real capacity.
|
|
*/
|
|
if (BadBattery)
|
|
{
|
|
LowDelta = LowCapDifference / BatteriesCount;
|
|
HighDelta = HighCapDifference / BatteriesCount;
|
|
}
|
|
else
|
|
{
|
|
if (TotalRate)
|
|
{
|
|
LowDelta = COMPUTE_BATT_CAP_DELTA(LowCapDifference, BatteryData, TotalRate);
|
|
HighDelta = COMPUTE_BATT_CAP_DELTA(HighCapDifference, BatteryData, TotalRate);
|
|
}
|
|
else
|
|
{
|
|
LowDelta = 0;
|
|
HighDelta = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Assign the wait low capacity setting ONLY if the battery delta
|
|
* is not high. Otherwise it has overflowed and we cannot use that
|
|
* for low capacity, of which we have to default it to 0.
|
|
*/
|
|
if (BatteryData->BatteryStatus.Capacity > LowDelta)
|
|
{
|
|
BatteryData->WaitStatus.LowCapacity = BatteryData->BatteryStatus.Capacity - LowDelta;
|
|
}
|
|
else
|
|
{
|
|
BatteryData->WaitStatus.LowCapacity = COMPBATT_WAIT_MIN_LOW_CAPACITY;
|
|
}
|
|
|
|
/*
|
|
* Assign the wait high capacity setting ONLY if the real capacity
|
|
* is not above the maximum highest capacity constant.
|
|
*/
|
|
HighestCapacity = COMPBATT_WAIT_MAX_HIGH_CAPACITY - HighDelta;
|
|
if (HighestCapacity < BatteryData->BatteryStatus.Capacity)
|
|
{
|
|
BatteryData->WaitStatus.HighCapacity = HighestCapacity;
|
|
}
|
|
else
|
|
{
|
|
BatteryData->WaitStatus.HighCapacity = BatteryData->BatteryStatus.Capacity + HighDelta;
|
|
}
|
|
|
|
/*
|
|
* We have set up the wait values but they are in conflict with the
|
|
* ones set up by the IRP complete worker. We have to cancel the IRP
|
|
* so the worker will copy our wait configuration values.
|
|
*/
|
|
if ((BatteryData->Mode == COMPBATT_READ_STATUS) &&
|
|
(BatteryData->WaitStatus.PowerState != BatteryData->WorkerBuffer.WorkerWaitStatus.PowerState ||
|
|
BatteryData->WaitStatus.LowCapacity != BatteryData->WorkerBuffer.WorkerWaitStatus.LowCapacity ||
|
|
BatteryData->WaitStatus.HighCapacity != BatteryData->WorkerBuffer.WorkerWaitStatus.HighCapacity))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_INFO)
|
|
DbgPrint("CompBatt: Configuration wait values are in conflict\n"
|
|
" BatteryData->WaitStatus.PowerState -> 0x%lx\n"
|
|
" BatteryData->WorkerBuffer.WorkerWaitStatus.PowerState -> 0x%lx\n"
|
|
" BatteryData->WaitStatus.LowCapacity -> %u\n"
|
|
" BatteryData->WorkerBuffer.WorkerWaitStatus.LowCapacity -> %u\n"
|
|
" BatteryData->WaitStatus.HighCapacity -> %u\n"
|
|
" BatteryData->WorkerBuffer.WorkerWaitStatus.HighCapacity -> %u\n",
|
|
BatteryData->WaitStatus.PowerState,
|
|
BatteryData->WorkerBuffer.WorkerWaitStatus.PowerState,
|
|
BatteryData->WaitStatus.LowCapacity,
|
|
BatteryData->WorkerBuffer.WorkerWaitStatus.LowCapacity,
|
|
BatteryData->WaitStatus.HighCapacity,
|
|
BatteryData->WorkerBuffer.WorkerWaitStatus.HighCapacity);
|
|
|
|
IoCancelIrp(BatteryData->Irp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BatteryData->WaitStatus.LowCapacity = BATTERY_UNKNOWN_CAPACITY;
|
|
BatteryData->WaitStatus.HighCapacity = BATTERY_UNKNOWN_CAPACITY;
|
|
}
|
|
}
|
|
|
|
/* We are done with this battery */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Release the lock as we are no longer poking through the batteries list */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Ensure the composite battery did not incur in drastic changes of tag */
|
|
if (!(DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED) ||
|
|
(DeviceExtension->Tag != BatteryTag))
|
|
{
|
|
/*
|
|
* Either the last battery was removed (in this case the composite is no
|
|
* longer existing) or a battery was removed of which the whole battery
|
|
* information must be recomputed and such.
|
|
*/
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Last battery or a battery was removed, the whole composite data must be recomputed\n");
|
|
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Queries the battery status of each individiual connected battery with
|
|
* the composite battery and combines all the retrieved data as one
|
|
* single battery status for the composite battery.
|
|
*
|
|
* @param[in] DeviceExtension
|
|
* A pointer to a device extension which describes the composite battery
|
|
* itself. It is used to gather each connected battery in the list with
|
|
* the composite battery.
|
|
*
|
|
* @param[in] Tag
|
|
* A battery tag supplied by the caller. This is typically the tag of
|
|
* the composite battery which is used to check against the cached tag
|
|
* of the composite battery if it has changed or not.
|
|
*
|
|
* @param[out] BatteryStatus
|
|
* A pointer to a battery status that contains the combined data, returned
|
|
* to the caller. It serves as the battery status for the composite battery.
|
|
*
|
|
* @return
|
|
* Returns STATUS_NO_SUCH_DEVICE if the supplied battery tag does not match
|
|
* with that of the cached composite battery's tag or if the composite
|
|
* battery currently does not have a tag assigned. Otherwise STATUS_SUCCESS
|
|
* is returned, which it will also return success if the composite battery's
|
|
* cached battery status is fresh which indicates it has already been computed.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattQueryStatus(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension,
|
|
_In_ ULONG Tag,
|
|
_Out_ PBATTERY_STATUS BatteryStatus)
|
|
{
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
BATTERY_WAIT_STATUS Wait;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
ULONGLONG LastReadTime, CurrentReadTime;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
/*
|
|
* The caller wants to update the composite battery status but the composite
|
|
* itself does not have a valid tag assigned, or the tag does not actually match.
|
|
*/
|
|
if (!(DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED) ||
|
|
(DeviceExtension->Tag != Tag))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Composite battery tag not assigned or not matching (Tag -> %lu, Composite Tag -> %lu)\n",
|
|
Tag, DeviceExtension->Tag);
|
|
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
/* Initialize the status and wait fields with zeros */
|
|
RtlZeroMemory(BatteryStatus, sizeof(*BatteryStatus));
|
|
RtlZeroMemory(&Wait, sizeof(Wait));
|
|
|
|
/*
|
|
* The battery status was already updated when the caller queried for new
|
|
* status. We do not need to update the status again for no reason.
|
|
* Just give them the data outright.
|
|
*/
|
|
CurrentReadTime = KeQueryInterruptTime();
|
|
LastReadTime = CurrentReadTime - DeviceExtension->InterruptTime;
|
|
if (LastReadTime < COMPBATT_FRESH_STATUS_TIME)
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Composite battery status data is fresh, no need to update it again\n");
|
|
|
|
RtlCopyMemory(BatteryStatus, &DeviceExtension->BatteryStatus, sizeof(BATTERY_STATUS));
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
* Initialize the battery status context with unknown defaults, until we get
|
|
* to retrieve the real data from each battery and compute the exact status.
|
|
* Assume the system is powered by AC source for now until we find out it is
|
|
* not the case.
|
|
*/
|
|
BatteryStatus->PowerState = BATTERY_POWER_ON_LINE;
|
|
BatteryStatus->Capacity = BATTERY_UNKNOWN_CAPACITY;
|
|
BatteryStatus->Voltage = BATTERY_UNKNOWN_VOLTAGE;
|
|
BatteryStatus->Rate = BATTERY_UNKNOWN_RATE;
|
|
|
|
/* Iterate over all the present linked batteries and retrieve their status */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
for (NextEntry = ListHead->Flink;
|
|
NextEntry != ListHead;
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
/* Acquire the remove lock so this battery does not disappear under us */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (!NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
continue;
|
|
|
|
/* Now release the device lock since the battery can't go away */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Setup the battery tag for the status wait which is needed to send off the IOCTL */
|
|
Wait.BatteryTag = BatteryData->Tag;
|
|
|
|
/* Make sure this battery has a tag before we send off the IOCTL */
|
|
if (BatteryData->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
/* Only query new battery status data if it is no longer fresh */
|
|
LastReadTime = CurrentReadTime - BatteryData->InterruptTime;
|
|
if (LastReadTime > COMPBATT_FRESH_STATUS_TIME)
|
|
{
|
|
RtlZeroMemory(&BatteryData->BatteryStatus,
|
|
sizeof(BatteryData->BatteryStatus));
|
|
Status = BatteryIoctl(IOCTL_BATTERY_QUERY_STATUS,
|
|
BatteryData->DeviceObject,
|
|
&Wait,
|
|
sizeof(Wait),
|
|
&BatteryData->BatteryStatus,
|
|
sizeof(BatteryData->BatteryStatus),
|
|
FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/*
|
|
* If the device is being suddenly removed then we must invalidate
|
|
* both this battery and composite tags.
|
|
*/
|
|
if (Status == STATUS_DEVICE_REMOVED)
|
|
{
|
|
Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
break;
|
|
}
|
|
|
|
/* Update the timestamp of the current read of battery status */
|
|
BatteryData->InterruptTime = CurrentReadTime;
|
|
}
|
|
|
|
/*
|
|
* Now it is time to combine the data into the composite status.
|
|
* The battery is either charging or discharging. AC is present
|
|
* only if the charger supplies current to all batteries. And
|
|
* the composite is deemed as critical if at least one battery
|
|
* is discharging and it is in crtitical state.
|
|
*/
|
|
BatteryStatus->PowerState |= (BatteryData->BatteryStatus.PowerState & (BATTERY_CHARGING | BATTERY_DISCHARGING));
|
|
BatteryStatus->PowerState &= (BatteryData->BatteryStatus.PowerState | ~BATTERY_POWER_ON_LINE);
|
|
if ((BatteryData->BatteryStatus.PowerState & BATTERY_CRITICAL) &&
|
|
(BatteryData->BatteryStatus.PowerState & BATTERY_DISCHARGING))
|
|
{
|
|
BatteryStatus->PowerState |= BATTERY_CRITICAL;
|
|
}
|
|
|
|
/* Add up the battery capacity if it is not unknown */
|
|
if (BatteryData->BatteryStatus.Capacity != BATTERY_UNKNOWN_CAPACITY)
|
|
{
|
|
if (BatteryStatus->Capacity != BATTERY_UNKNOWN_CAPACITY)
|
|
{
|
|
BatteryStatus->Capacity += BatteryData->BatteryStatus.Capacity;
|
|
}
|
|
else
|
|
{
|
|
BatteryStatus->Capacity = BatteryData->BatteryStatus.Capacity;
|
|
}
|
|
}
|
|
|
|
/* Always pick up the greatest voltage for the composite battery */
|
|
if (BatteryData->BatteryStatus.Voltage != BATTERY_UNKNOWN_VOLTAGE)
|
|
{
|
|
if (BatteryStatus->Voltage != BATTERY_UNKNOWN_VOLTAGE)
|
|
{
|
|
BatteryStatus->Voltage = max(BatteryStatus->Voltage,
|
|
BatteryData->BatteryStatus.Voltage);
|
|
}
|
|
else
|
|
{
|
|
BatteryStatus->Voltage = BatteryData->BatteryStatus.Voltage;
|
|
}
|
|
}
|
|
|
|
/* Add up the battery discharge rate if it is not unknown */
|
|
if (BatteryData->BatteryStatus.Rate != BATTERY_UNKNOWN_RATE)
|
|
{
|
|
if (BatteryStatus->Rate != BATTERY_UNKNOWN_RATE)
|
|
{
|
|
BatteryStatus->Rate += BatteryData->BatteryStatus.Rate;
|
|
}
|
|
else
|
|
{
|
|
BatteryStatus->Rate = BatteryData->BatteryStatus.Rate;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We are done combining data from this battery */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Release the lock as we are no longer poking through the batteries list */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Ensure the composite battery did not incur in drastic changes of tag */
|
|
if (!(DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED) ||
|
|
(DeviceExtension->Tag != Tag))
|
|
{
|
|
/*
|
|
* Either the last battery was removed (in this case the composite is no
|
|
* longer existing) or a battery was removed of which the whole battery
|
|
* information must be recomputed and such.
|
|
*/
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: Last battery or a battery was removed, the whole composite data must be recomputed\n");
|
|
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
/*
|
|
* If there is a battery that is charging while another one discharging,
|
|
* then tell the caller the composite battery is actually discharging.
|
|
* This is less likely to happen on a multi-battery system like laptops
|
|
* as the charger would provide electricity to all the batteries.
|
|
* Perhaps the most likely case scenario would be if the system were
|
|
* to be powered by a UPS.
|
|
*/
|
|
if ((BatteryStatus->PowerState & BATTERY_CHARGING) &&
|
|
(BatteryStatus->PowerState & BATTERY_DISCHARGING))
|
|
{
|
|
BatteryStatus->PowerState &= ~BATTERY_CHARGING;
|
|
}
|
|
|
|
/* Copy the combined status information to the composite battery */
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
RtlCopyMemory(&DeviceExtension->BatteryStatus,
|
|
BatteryStatus,
|
|
sizeof(DeviceExtension->BatteryStatus));
|
|
|
|
/* Update the last read battery status timestamp as well */
|
|
DeviceExtension->InterruptTime = CurrentReadTime;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattGetBatteryInformation(
|
|
_Out_ PBATTERY_INFORMATION BatteryInfo,
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
BATTERY_QUERY_INFORMATION InputBuffer;
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING GetBatteryInformation\n");
|
|
|
|
/* Set defaults */
|
|
BatteryInfo->DefaultAlert1 = 0;
|
|
BatteryInfo->DefaultAlert2 = 0;
|
|
BatteryInfo->CriticalBias = 0;
|
|
|
|
/* Loop the battery list */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
NextEntry = ListHead->Flink;
|
|
while (NextEntry != ListHead)
|
|
{
|
|
/* Try to acquire the remove lock */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
{
|
|
/* Now release the device lock since the battery can't go away */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Build the query */
|
|
InputBuffer.BatteryTag = BatteryData->Tag;
|
|
InputBuffer.InformationLevel = BatteryInformation;
|
|
InputBuffer.AtRate = 0;
|
|
|
|
/* Make sure the battery has a tag */
|
|
if (BatteryData->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
/* Do we already have the data? */
|
|
if (!(BatteryData->Flags & COMPBATT_BATTERY_INFORMATION_PRESENT))
|
|
{
|
|
/* Send the IOCTL to query the information */
|
|
RtlZeroMemory(&BatteryData->BatteryInformation,
|
|
sizeof(BatteryData->BatteryInformation));
|
|
Status = BatteryIoctl(IOCTL_BATTERY_QUERY_INFORMATION,
|
|
BatteryData->DeviceObject,
|
|
&InputBuffer,
|
|
sizeof(InputBuffer),
|
|
&BatteryData->BatteryInformation,
|
|
sizeof(BatteryData->BatteryInformation),
|
|
FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Fail if the query had a problem */
|
|
if (Status == STATUS_DEVICE_REMOVED) Status = STATUS_NO_SUCH_DEVICE;
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
break;
|
|
}
|
|
|
|
/* Next time we can use the static copy */
|
|
BatteryData->Flags |= COMPBATT_BATTERY_INFORMATION_PRESENT;
|
|
if (CompBattDebug & COMPBATT_DEBUG_INFO)
|
|
DbgPrint("CompBattGetBatteryInformation: Read individual BATTERY_INFORMATION\n"
|
|
"-------- Capabilities = %x\n-------- Technology = %x\n"
|
|
"-------- Chemistry[4] = %x\n-------- DesignedCapacity = %x\n"
|
|
"-------- FullChargedCapacity = %x\n-------- DefaultAlert1 = %x\n"
|
|
"-------- DefaultAlert2 = %x\n-------- CriticalBias = %x\n"
|
|
"-------- CycleCount = %x\n",
|
|
BatteryData->BatteryInformation.Capabilities,
|
|
BatteryData->BatteryInformation.Technology,
|
|
BatteryData->BatteryInformation.Chemistry,
|
|
BatteryData->BatteryInformation.DesignedCapacity,
|
|
BatteryData->BatteryInformation.FullChargedCapacity,
|
|
BatteryData->BatteryInformation.DefaultAlert1,
|
|
BatteryData->BatteryInformation.DefaultAlert2,
|
|
BatteryData->BatteryInformation.CriticalBias,
|
|
BatteryData->BatteryInformation.CycleCount);
|
|
}
|
|
|
|
/* Combine capabilities */
|
|
BatteryInfo->Capabilities |= BatteryData->BatteryInformation.Capabilities;
|
|
|
|
/* Add-on capacity */
|
|
if (BatteryData->BatteryInformation.DesignedCapacity != BATTERY_UNKNOWN_CAPACITY)
|
|
{
|
|
BatteryInfo->DesignedCapacity += BatteryData->BatteryInformation.DesignedCapacity;
|
|
}
|
|
|
|
/* Add on fully charged capacity */
|
|
if (BatteryData->BatteryInformation.FullChargedCapacity != BATTERY_UNKNOWN_CAPACITY)
|
|
{
|
|
BatteryInfo->FullChargedCapacity += BatteryData->BatteryInformation.FullChargedCapacity;
|
|
}
|
|
|
|
/* Choose the highest alert */
|
|
BatteryInfo->DefaultAlert1 = max(BatteryInfo->DefaultAlert1,
|
|
BatteryData->BatteryInformation.DefaultAlert1);
|
|
|
|
/* Choose the highest alert */
|
|
BatteryInfo->DefaultAlert2 = max(BatteryInfo->DefaultAlert2,
|
|
BatteryData->BatteryInformation.DefaultAlert2);
|
|
|
|
/* Choose the highest critical bias */
|
|
BatteryInfo->CriticalBias = max(BatteryInfo->CriticalBias,
|
|
BatteryData->BatteryInformation.CriticalBias);
|
|
}
|
|
|
|
/* Re-acquire the device extension lock and release the remove lock */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Next entry */
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
/* We are done with the list, check if the information was queried okay */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* If there's no fully charged capacity, use the design capacity */
|
|
if (!BatteryInfo->FullChargedCapacity)
|
|
{
|
|
BatteryInfo->FullChargedCapacity = BatteryInfo->DesignedCapacity;
|
|
}
|
|
|
|
/* Print out final combined data */
|
|
if (CompBattDebug & COMPBATT_DEBUG_INFO)
|
|
DbgPrint("CompBattGetBatteryInformation: Returning BATTERY_INFORMATION\n"
|
|
"-------- Capabilities = %x\n-------- Technology = %x\n"
|
|
"-------- Chemistry[4] = %x\n-------- DesignedCapacity = %x\n"
|
|
"-------- FullChargedCapacity = %x\n-------- DefaultAlert1 = %x\n"
|
|
"-------- DefaultAlert2 = %x\n-------- CriticalBias = %x\n"
|
|
"-------- CycleCount = %x\n",
|
|
BatteryInfo->Capabilities,
|
|
BatteryInfo->Technology,
|
|
BatteryInfo->Chemistry,
|
|
BatteryInfo->DesignedCapacity,
|
|
BatteryInfo->FullChargedCapacity,
|
|
BatteryInfo->DefaultAlert1,
|
|
BatteryInfo->DefaultAlert2,
|
|
BatteryInfo->CriticalBias,
|
|
BatteryInfo->CycleCount);
|
|
|
|
/* Copy the data into the device extension */
|
|
RtlCopyMemory(&DeviceExtension->BatteryInformation,
|
|
BatteryInfo,
|
|
sizeof(DeviceExtension->BatteryInformation));
|
|
DeviceExtension->Flags |= COMPBATT_BATTERY_INFORMATION_PRESENT;
|
|
}
|
|
|
|
/* We are done */
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING GetBatteryInformation\n");
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattGetBatteryGranularity(
|
|
_Out_ PBATTERY_REPORTING_SCALE ReportingScale,
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
BATTERY_QUERY_INFORMATION InputBuffer;
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
BATTERY_REPORTING_SCALE BatteryScale[4];
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
ULONG i;
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING GetBatteryGranularity\n");
|
|
|
|
/* Set defaults */
|
|
ReportingScale[0].Granularity = -1;
|
|
ReportingScale[1].Granularity = -1;
|
|
ReportingScale[2].Granularity = -1;
|
|
ReportingScale[3].Granularity = -1;
|
|
|
|
/* Loop the battery list */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
NextEntry = ListHead->Flink;
|
|
while (NextEntry != ListHead)
|
|
{
|
|
/* Try to acquire the remove lock */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
{
|
|
/* Now release the device lock since the battery can't go away */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Build the query */
|
|
InputBuffer.BatteryTag = BatteryData->Tag;
|
|
InputBuffer.InformationLevel = BatteryGranularityInformation;
|
|
|
|
/* Make sure the battery has a tag */
|
|
if (BatteryData->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
/* Send the IOCTL to query the information */
|
|
RtlZeroMemory(&BatteryData->BatteryInformation,
|
|
sizeof(BatteryData->BatteryInformation));
|
|
Status = BatteryIoctl(IOCTL_BATTERY_QUERY_INFORMATION,
|
|
BatteryData->DeviceObject,
|
|
&InputBuffer,
|
|
sizeof(InputBuffer),
|
|
&BatteryScale,
|
|
sizeof(BatteryScale),
|
|
FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/* Fail if the query had a problem */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
break;
|
|
}
|
|
|
|
/* Loop all 4 scales */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
/* Check for valid granularity */
|
|
if (BatteryScale[i].Granularity)
|
|
{
|
|
/* If it's smaller, use it instead */
|
|
ReportingScale[i].Granularity = min(BatteryScale[i].Granularity,
|
|
ReportingScale[i].Granularity);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/* Re-acquire the device extension lock and release the remove lock */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Next entry */
|
|
NextEntry = NextEntry->Flink;
|
|
}
|
|
|
|
/* All done */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING GetBatteryGranularity\n");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Calculates the "At Rate" flow of the composite battery based on the
|
|
* sum of all connected batteries, in order to retrieve the precise
|
|
* battery time estimation.
|
|
*
|
|
* @param[in] DeviceExtension
|
|
* A pointer to a device extension which describes the composite battery
|
|
* itself. It is used to gather each connected battery in the list with
|
|
* the composite battery.
|
|
*
|
|
* @return
|
|
* Returns the computed "At Rate" flow to the caller.
|
|
*/
|
|
static
|
|
LONG
|
|
CompBattCalculateAtRateTime(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
NTSTATUS Status;
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
BATTERY_QUERY_INFORMATION QueryInformation;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
ULONG Time;
|
|
LONG ComputedAtRate = 0;
|
|
|
|
/* Walk over the linked batteries list to poll for "At Rate" value of each battery */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
for (NextEntry = ListHead->Flink;
|
|
NextEntry != ListHead;
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
/* Acquire the remove lock so this battery does not disappear under us */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (!NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
continue;
|
|
|
|
/* Now release the device lock since the battery can't go away */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Build the necessary information in order to query the battery estimated time */
|
|
QueryInformation.BatteryTag = BatteryData->Tag;
|
|
QueryInformation.InformationLevel = BatteryEstimatedTime;
|
|
QueryInformation.AtRate = 0;
|
|
|
|
/* Make sure this battery has a valid tag before issuing the IOCTL */
|
|
if (BatteryData->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
/*
|
|
* Now it is time to issue the IOCTL to the battery device.
|
|
* We are calculating the "At Rate" counter based on each linked
|
|
* battery that is discharging, one at a time. This ensures
|
|
* that when we will actually retrieve the estimation time of each
|
|
* individual battery and sum it all up as one time for the composite
|
|
* battery, that the estimated time is accurate enough.
|
|
*/
|
|
Status = BatteryIoctl(IOCTL_BATTERY_QUERY_INFORMATION,
|
|
BatteryData->DeviceObject,
|
|
&QueryInformation,
|
|
sizeof(QueryInformation),
|
|
&Time,
|
|
sizeof(Time),
|
|
FALSE);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
if ((Time != 0) && (Time != BATTERY_UNKNOWN_TIME))
|
|
{
|
|
ComputedAtRate -= COMPUTE_ATRATE_DRAIN(BatteryData, Time);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We are done with this battery */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Release the lock as we are no longer poking through the batteries list */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
return ComputedAtRate;
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Retrieves the estimated time of the composite battery based on the
|
|
* power drain rate of all the batteries present in the system.
|
|
*
|
|
* @param[out] Time
|
|
* A pointer to the computed estimated time of the composite battery,
|
|
* returned to caller. Note that if there are not any batteries that
|
|
* are draining power, or if the system is powered by external AC source,
|
|
* the estimated time is unknown
|
|
*
|
|
* @param[in] DeviceExtension
|
|
* A pointer to a device extension which describes the composite battery
|
|
* itself. It is used to gather each connected battery in the list with
|
|
* the composite battery.
|
|
*
|
|
* @return
|
|
* Returns STATUS_NO_SUCH_DEVICE if the supplied battery tag does not match
|
|
* with that of the cached composite battery's tag or if the composite
|
|
* battery currently does not have a tag assigned. Otherwise STATUS_SUCCESS
|
|
* is returned.
|
|
*/
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattGetEstimatedTime(
|
|
_Out_ PULONG Time,
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension)
|
|
{
|
|
NTSTATUS Status;
|
|
PCOMPBATT_BATTERY_DATA BatteryData;
|
|
BATTERY_STATUS BatteryStatus;
|
|
BATTERY_QUERY_INFORMATION QueryInformation;
|
|
PLIST_ENTRY ListHead, NextEntry;
|
|
ULONG ReturnedTime;
|
|
LONG ComputedAtRate;
|
|
|
|
/* Assume the battery time is not estimated yet */
|
|
*Time = BATTERY_UNKNOWN_TIME;
|
|
|
|
/*
|
|
* Before we are querying the composite estimated battery time we must
|
|
* refresh the battery status cache if we have not done it so.
|
|
*/
|
|
Status = CompBattQueryStatus(DeviceExtension,
|
|
DeviceExtension->Tag,
|
|
&BatteryStatus);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_ERR)
|
|
DbgPrint("CompBatt: Failed to refresh composite battery's status (Status 0x%08lx)\n", Status);
|
|
|
|
return Status;
|
|
}
|
|
|
|
/* Print out battery status data that has been polled */
|
|
if (CompBattDebug & COMPBATT_DEBUG_INFO)
|
|
DbgPrint("CompBatt: Latest composite battery status (when querying for estimated time)\n"
|
|
" PowerState -> 0x%lx\n"
|
|
" Capacity -> %u\n"
|
|
" Voltage -> %u\n"
|
|
" Rate -> %d\n",
|
|
BatteryStatus.PowerState,
|
|
BatteryStatus.Capacity,
|
|
BatteryStatus.Voltage,
|
|
BatteryStatus.Rate);
|
|
|
|
/*
|
|
* If the batteries are not being discharged and the system is directly
|
|
* being powered by external AC source then it makes no sense to
|
|
* compute the battery estimated time because that construct is for
|
|
* WHEN the system is powered directly from batteries and it drains power.
|
|
*/
|
|
if (DeviceExtension->BatteryStatus.PowerState & BATTERY_POWER_ON_LINE)
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
{
|
|
DbgPrint("CompBatt: The system is powered by AC source, estimated time is not available\n");
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Determine the draining "At Rate" counter for all batteries */
|
|
ComputedAtRate = CompBattCalculateAtRateTime(DeviceExtension);
|
|
|
|
/*
|
|
* A rate of 0 indicates none of the batteries that are linked with
|
|
* the composite are being drained therefore we cannot estimate the
|
|
* run time of the composite as it is not discharging.
|
|
*/
|
|
if (ComputedAtRate == 0)
|
|
{
|
|
if (CompBattDebug & COMPBATT_DEBUG_WARN)
|
|
DbgPrint("CompBatt: No battery is discharging and no power is being drained, cannot estimate the run time\n");
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* Walk over the linked batteries list and determine the exact estimated time */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
ListHead = &DeviceExtension->BatteryList;
|
|
for (NextEntry = ListHead->Flink;
|
|
NextEntry != ListHead;
|
|
NextEntry = NextEntry->Flink)
|
|
{
|
|
/* Acquire the remove lock so this battery does not disappear under us */
|
|
BatteryData = CONTAINING_RECORD(NextEntry, COMPBATT_BATTERY_DATA, BatteryLink);
|
|
if (!NT_SUCCESS(IoAcquireRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp)))
|
|
continue;
|
|
|
|
/* Now release the device lock since the battery can't go away */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
|
|
/* Build the necessary information in order to query the battery estimated time */
|
|
QueryInformation.BatteryTag = BatteryData->Tag;
|
|
QueryInformation.InformationLevel = BatteryEstimatedTime;
|
|
QueryInformation.AtRate = ComputedAtRate;
|
|
|
|
/* Make sure this battery has a valid tag before issuing the IOCTL */
|
|
if (BatteryData->Tag != BATTERY_TAG_INVALID)
|
|
{
|
|
Status = BatteryIoctl(IOCTL_BATTERY_QUERY_INFORMATION,
|
|
BatteryData->DeviceObject,
|
|
&QueryInformation,
|
|
sizeof(QueryInformation),
|
|
&ReturnedTime,
|
|
sizeof(ReturnedTime),
|
|
FALSE);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
/*
|
|
* If the device is being suddenly removed then we must invalidate
|
|
* both this battery and composite tags.
|
|
*/
|
|
if (Status == STATUS_DEVICE_REMOVED)
|
|
{
|
|
Status = STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
|
|
/*
|
|
* In other places we are ceasing the execution of the loop but
|
|
* here we want to continue looking for other linked batteries.
|
|
* This is because we are querying for the estimated battery time
|
|
* at the time the last battery status was valid. Also bear in
|
|
* mind IOCTL_BATTERY_QUERY_INFORMATION with InformationLevel as
|
|
* BatteryEstimatedTime might not be a valid request supported
|
|
* by this battery.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/* Now sum up the estimated battery time */
|
|
if (ReturnedTime != BATTERY_UNKNOWN_TIME)
|
|
{
|
|
if (*Time != BATTERY_UNKNOWN_TIME)
|
|
{
|
|
*Time += ReturnedTime;
|
|
}
|
|
else
|
|
{
|
|
*Time = ReturnedTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* We are done with this battery */
|
|
ExAcquireFastMutex(&DeviceExtension->Lock);
|
|
IoReleaseRemoveLock(&BatteryData->RemoveLock, BatteryData->Irp);
|
|
}
|
|
|
|
/* Release the lock as we are no longer poking through the batteries list */
|
|
ExReleaseFastMutex(&DeviceExtension->Lock);
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
CompBattQueryInformation(
|
|
_In_ PCOMPBATT_DEVICE_EXTENSION DeviceExtension,
|
|
_In_ ULONG Tag,
|
|
_In_ BATTERY_QUERY_INFORMATION_LEVEL InfoLevel,
|
|
_In_opt_ LONG AtRate,
|
|
_In_ PVOID Buffer,
|
|
_In_ ULONG BufferLength,
|
|
_Out_ PULONG ReturnedLength)
|
|
{
|
|
BATTERY_INFORMATION BatteryInfo;
|
|
BATTERY_REPORTING_SCALE BatteryGranularity[4];
|
|
PWCHAR BatteryName = L"Composite Battery";
|
|
//BATTERY_MANUFACTURE_DATE Date;
|
|
ULONG Dummy, Time;
|
|
PVOID QueryData = NULL;
|
|
ULONG QueryLength = 0;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PAGED_CODE();
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: ENTERING QueryInformation\n");
|
|
|
|
/* Check for valid/correct tag */
|
|
if ((Tag != DeviceExtension->Tag) ||
|
|
(!(DeviceExtension->Flags & COMPBATT_TAG_ASSIGNED)))
|
|
{
|
|
/* Not right, so fail */
|
|
return STATUS_NO_SUCH_DEVICE;
|
|
}
|
|
|
|
/* Check what caller wants */
|
|
switch (InfoLevel)
|
|
{
|
|
case BatteryInformation:
|
|
|
|
/* Query combined battery information */
|
|
RtlZeroMemory(&BatteryInfo, sizeof(BatteryInfo));
|
|
Status = CompBattGetBatteryInformation(&BatteryInfo, DeviceExtension);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Return the data if successful */
|
|
QueryData = &BatteryInfo;
|
|
QueryLength = sizeof(BatteryInfo);
|
|
}
|
|
break;
|
|
|
|
case BatteryGranularityInformation:
|
|
|
|
/* Query combined granularity information */
|
|
RtlZeroMemory(&BatteryGranularity, sizeof(BatteryGranularity));
|
|
Status = CompBattGetBatteryGranularity(BatteryGranularity, DeviceExtension);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Return the data if successful */
|
|
QueryLength = sizeof(BatteryGranularity);
|
|
QueryData = &BatteryGranularity;
|
|
}
|
|
break;
|
|
|
|
case BatteryEstimatedTime:
|
|
|
|
/* Query combined time estimate information */
|
|
RtlZeroMemory(&Time, sizeof(Time));
|
|
Status = CompBattGetEstimatedTime(&Time, DeviceExtension);
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
/* Return the data if successful */
|
|
QueryLength = sizeof(Time);
|
|
QueryData = &Time;
|
|
}
|
|
break;
|
|
|
|
case BatteryManufactureName:
|
|
case BatteryDeviceName:
|
|
|
|
/* Return the static buffer */
|
|
QueryData = BatteryName;
|
|
QueryLength = sizeof(L"Composite Battery");
|
|
break;
|
|
|
|
case BatteryManufactureDate:
|
|
|
|
/* Static data */
|
|
//Date.Day = 26;
|
|
//Date.Month = 06;
|
|
//Date.Year = 1997;
|
|
break;
|
|
|
|
case BatteryTemperature:
|
|
case BatteryUniqueID:
|
|
|
|
/* Return zero */
|
|
Dummy = 0;
|
|
QueryData = &Dummy;
|
|
QueryLength = sizeof(Dummy);
|
|
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 */
|
|
if (CompBattDebug & COMPBATT_DEBUG_TRACE) DbgPrint("CompBatt: EXITING QueryInformation\n");
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
DriverEntry(
|
|
_In_ PDRIVER_OBJECT DriverObject,
|
|
_In_ PUNICODE_STRING RegistryPath)
|
|
{
|
|
/* Register add device routine */
|
|
DriverObject->DriverExtension->AddDevice = CompBattAddDevice;
|
|
|
|
/* Register other handlers */
|
|
DriverObject->MajorFunction[IRP_MJ_CREATE] = CompBattOpenClose;
|
|
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CompBattOpenClose;
|
|
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = CompBattIoctl;
|
|
DriverObject->MajorFunction[IRP_MJ_POWER] = CompBattPowerDispatch;
|
|
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = CompBattSystemControl;
|
|
DriverObject->MajorFunction[IRP_MJ_PNP] = CompBattPnpDispatch;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
/* EOF */
|