[COMPBATT] Implement CompBattQueryStatus and CompBattSetStatusNotify

This commit is contained in:
George Bișoc 2024-12-14 22:48:52 +01:00
parent ee9914c712
commit a6d4998c6c
No known key found for this signature in database
GPG key ID: 688C4FBE25D7DEF6

View file

@ -481,6 +481,115 @@ CompBattDisableStatusNotify(
return STATUS_SUCCESS;
}
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;
}
NTSTATUS
NTAPI
CompBattSetStatusNotify(
@ -488,8 +597,226 @@ CompBattSetStatusNotify(
_In_ ULONG BatteryTag,
_In_ PBATTERY_NOTIFY BatteryNotify)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
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;
}
NTSTATUS
@ -499,8 +826,216 @@ CompBattQueryStatus(
_In_ ULONG Tag,
_Out_ PBATTERY_STATUS BatteryStatus)
{
UNIMPLEMENTED;
return STATUS_NOT_IMPLEMENTED;
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