https://git.reactos.org/?p=reactos.git;a=commitdiff;h=a6d4998c6c89abfb51cb561ce19f1f5aabc1bbe3
commit a6d4998c6c89abfb51cb561ce19f1f5aabc1bbe3 Author: George Bișoc <george.bi...@reactos.org> AuthorDate: Sat Dec 14 22:48:52 2024 +0100 Commit: George Bișoc <george.bi...@reactos.org> CommitDate: Wed Jan 8 23:20:08 2025 +0100 [COMPBATT] Implement CompBattQueryStatus and CompBattSetStatusNotify --- drivers/bus/acpi/compbatt/compbatt.c | 543 ++++++++++++++++++++++++++++++++++- 1 file changed, 539 insertions(+), 4 deletions(-) diff --git a/drivers/bus/acpi/compbatt/compbatt.c b/drivers/bus/acpi/compbatt/compbatt.c index 9e0e5d3a782..7e55887be60 100644 --- a/drivers/bus/acpi/compbatt/compbatt.c +++ b/drivers/bus/acpi/compbatt/compbatt.c @@ -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