/*++ Copyright (c) 1990 Microsoft Corporation Module Name: pbatt.c Abstract: This module interfaces the policy manager to the composite device Author: Ken Reneris (kenr) 17-Jan-1997 Revision History: --*/ #include "pop.h" // // Internal prototypes // VOID PopRecalculateCBTriggerLevels ( ULONG Flags ); VOID PopComputeCBTime ( VOID ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, PopRecalculateCBTriggerLevels) #pragma alloc_text(PAGE, PopCompositeBatteryDeviceHandler) #pragma alloc_text(PAGE, PopComputeCBTime) #pragma alloc_text(PAGE, PopResetCBTriggers) #pragma alloc_text(PAGE, PopCurrentPowerState) #endif VOID PopCompositeBatteryUpdateThrottleLimit( IN ULONG CurrentCapacity ) /*++ Routine Description: This routine is called to update the ThrottleLimit in each of the processor's PRCB to reflect the maximum given the change in current capacity. This function was broken out because it cannot be paged code Arguments: CurrentCapacity - PercentageCapacity remaining... Return Value: None --*/ { KAFFINITY currentAffinity; KAFFINITY processors; PPROCESSOR_PERF_STATE perfStates; PPROCESSOR_POWER_STATE pState; ULONG perfStatesCount; ULONG i; KIRQL oldIrql; #if DBG ULONGLONG currentTime; UCHAR t[40]; currentTime = KeQueryInterruptTime(); PopTimeString(t, currentTime); #endif currentAffinity = 1; processors = KeActiveProcessors; while (processors) { if (!(processors & currentAffinity)) { currentAffinity <<= 1; continue; } KeSetSystemAffinityThread( currentAffinity ); processors &= ~currentAffinity; currentAffinity <<= 1; // // We need to be running at DISPATCH_LEVEL to access the // structures referenced within the pState... // KeRaiseIrql( DISPATCH_LEVEL, &oldIrql ); pState = &(KeGetCurrentPrcb()->PowerState); // // Does this processor support throttling? // if ((pState->Flags & PSTATE_SUPPORTS_THROTTLE) == 0) { // // No, then we don't care about it... // KeLowerIrql( oldIrql ); continue; } // // Look at the power structure and get the array of // perf states supported. Note that we change the // perfStatesCount by subtracting one so that we don't // have to worry about overrruning the array during // the for loop // pState = &(KeGetCurrentPrcb()->PowerState); perfStates = pState->PerfStates; perfStatesCount = (pState->PerfStatesCount - 1); // // See which throttle point is best for this power // capacity. Note that we have pre-calculated which // capacity matches which state, so its only a matter // of walking the array... // for (i = pState->KneeThrottleIndex; i < perfStatesCount; i++) { if (perfStates[i].MinCapacity <= CurrentCapacity) { break; } } // // Update the throttle limit index // if (pState->ThrottleLimitIndex != i) { pState->ThrottleLimitIndex = (UCHAR) i; #if DBG PoPrint( PO_THROTTLE, ("PopApplyThermalThrottle - %s - New Limit (%d) Index (%d)\n", t,perfStates[i].PercentFrequency,i) ); #endif // // Force a throttle update // PopUpdateProcessorThrottle(); } // // Revert back to our previous IRQL // KeLowerIrql( oldIrql ); } // while // // Revert to the affinity of the original thread // KeRevertToUserAffinityThread(); } VOID PopCompositeBatteryDeviceHandler ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, IN PVOID Context ) /*++ Routine Description: This function is the irp handler function to handle the completion if the composite battery irp. When there is a composite battery present one IRP is always outstanding to the device. On completion this IRP is recycled to the next request. N.B. PopPolicyLock must be held. Arguments: DeviceObject - DeviceObject of the battery device Irp - Irp which has completed Context - n/a Return Value: None. --*/ { PIO_STACK_LOCATION IrpSp, IrpPrevSp; PVOID InputBuffer, OutputBuffer; ULONG InputBufferLength, OutputBufferLength; ULONG IoctlCode; PSYSTEM_POWER_POLICY Policy; PPROCESSOR_POWER_POLICY ProcessorPolicy; ULONG i, j; NTSTATUS Status; ULONG currentCapacity; #if DBG ULONGLONG currentTime; UCHAR t[40]; currentTime = KeQueryInterruptTime(); PopTimeString(t, currentTime); #endif ASSERT_POLICY_LOCK_OWNED(); ASSERT (Irp == PopCB.StatusIrp); IrpSp = IoGetCurrentIrpStackLocation(Irp); if (NT_SUCCESS(Irp->IoStatus.Status)) { // // Handle the completed request // switch (PopCB.State) { case PO_CB_READ_TAG: case PO_CB_WAIT_TAG: // tag is now valid, read info PoPrint(PO_BATT, ("PopCB: New battery tag\n")); // // Reset the triggers. // // We reset PO_TRG_SET, so the level will be recalculated, but on // battery tag change we don't want to make the actions happen again. // If the trigger has not yet been set off, it will still be armed. // PopResetCBTriggers (PO_TRG_SET); PopCB.State = PO_CB_READ_INFO; PopCB.Tag = PopCB.u.Tag; // Ensure that the power state passed in is bad, so QUERY_STATUS // will return immediately. PopCB.Status.PowerState = (ULONG) -1; break; case PO_CB_READ_INFO: // info is read directly into buffer PoPrint(PO_BATT, ("PopCB: info read\n")); PopCB.State = PO_CB_READ_STATUS; RtlCopyMemory (&PopCB.Info, &PopCB.u.Info, sizeof(PopCB.Info)); break; case PO_CB_READ_STATUS: // // Status has been read, check it // PoPrint(PO_BATT, ("PopCB: Status PwrState %x, Cap %x, Volt %x, Cur %x\n", PopCB.u.Status.PowerState, PopCB.u.Status.Capacity, PopCB.u.Status.Voltage, PopCB.u.Status.Current )); PopCB.StatusTime = KeQueryInterruptTime(); // // Check if the current policy should be ac or dc // if (PopCB.u.Status.PowerState & BATTERY_POWER_ON_LINE) { ProcessorPolicy = &PopAcProcessorPolicy; Policy = &PopAcPolicy; } else { ProcessorPolicy = &PopDcProcessorPolicy; Policy = &PopDcPolicy; } // // Did the policy change? // if (PopPolicy != Policy || PopProcessorPolicy != ProcessorPolicy) { // // Change the active policy and reset the battery triggers // PopProcessorPolicy = ProcessorPolicy; PopPolicy = Policy; // // Reset triggers. // // In this case we re-arm both the user and system triggers. // The system trigger will be disarmed when we recalculate // trigger levels if the capacity was already below that level. // PopResetCBTriggers (PO_TRG_SET | PO_TRG_USER | PO_TRG_SYSTEM); PopSetNotificationWork ( PO_NOTIFY_ACDC_CALLBACK | PO_NOTIFY_POLICY | PO_NOTIFY_PROCESSOR_POLICY ); // // Recompute thermal throttle and cooling mode // // Note that PopApplyThermalThrottle will take care of any dynamic // throttling that might need to happen due to the AC/DC transition. // PopApplyThermalThrottle (); PopIdleUpdateIdleHandlers(); // // Recompute system idle values // PopInitSIdle (); } // // Did battery cross resolution setting? // Correction... Has it changed at all. If so, all apps should be updated, // even if it hasn't crossed a resolution setting. Otherwise, if one app // queries the current status, it could be displaying a different value than // the battery meter. // if ((PopCB.u.Status.Capacity != PopCB.Status.Capacity) || PopCB.Status.PowerState != PopCB.u.Status.PowerState) { PopSetNotificationWork (PO_NOTIFY_BATTERY_STATUS); PopCB.State = PO_CB_READ_EST_TIME; } PopRecalculateCBTriggerLevels (PO_TRG_SYSTEM); // // Update current battery status // memcpy (&PopCB.Status, &PopCB.u.Status, sizeof (PopCB.Status)); // // Check for discharging and if any discharge policies have tripped // if (Policy == &PopDcPolicy) { for (i=0; i < PO_NUM_POWER_LEVELS; i++) { if (PopCB.Status.Capacity <= PopCB.Trigger[i].Battery.Level) { // // Fire this power action // PopSetPowerAction( &PopCB.Trigger[i], PO_NOTIFY_BATTERY_STATUS, &Policy->DischargePolicy[i].PowerPolicy, Policy->DischargePolicy[i].MinSystemState, SubstituteLightestOverallDownwardBounded ); PopCB.State = PO_CB_READ_EST_TIME; } else { // // Clear the trigger for this event // PopCB.Trigger[i].Flags &= ~(PO_TRG_USER|PO_TRG_SYSTEM); } } // // Figure out what our current capacity is... // if (PopCB.Info.FullChargedCapacity) { currentCapacity = PopCB.Status.Capacity * 100 / PopCB.Info.FullChargedCapacity; } else { // // Assume that the battery is fully charged... // This will cause us to reset the throttle limiter // currentCapacity = 100; } } else { // // Assume that the battery is fully charged... // This will cause us to reset the throttle limiter // currentCapacity = 100; } // // This is kind of silly code to put in here, but since // want to minize our synchronization elsewhere, we have // to examine every processor's powerstate and update // the throttlelimitindex on each. This may be actually // a smarth thing to do if not all processors support // the same set of states // PopCompositeBatteryUpdateThrottleLimit( currentCapacity ); // // If there's a thread waiting or if we notified user (since // the response to the notify will be to read the power status) for // power state, read the est time now else read new status // if (PopCB.ThreadWaiting) { PopCB.State = PO_CB_READ_EST_TIME; } break; case PO_CB_READ_EST_TIME: // // Estimated time is read after sucessful status // read and (currently) only when there's a thread // waiting for the system power state // PoPrint(PO_BATT, ("PopCB: EstTime read\n")); PopCB.EstTime = PopCB.u.EstTime; PopCB.EstTimeTime = KeQueryInterruptTime(); PopComputeCBTime(); // // Signal waiting threads // PopCB.ThreadWaiting = FALSE; KeSetEvent (&PopCB.Event, 0, FALSE); // // Go back are read status // PopCB.State = PO_CB_READ_STATUS; break; default: PopInternalAddToDumpFile( Irp, sizeof(IRP), DeviceObject, NULL, NULL, NULL ); KeBugCheckEx( INTERNAL_POWER_ERROR, 0x300, POP_BATT, (ULONG_PTR)DeviceObject, (ULONG_PTR)Irp ); break; } } else { // // some sort of error, if the request was canceld re-issue // it else backup to reinitialize // if (Irp->IoStatus.Status != STATUS_CANCELLED) { // // This occurs under two circumstances. It is either the first time // through, or a battery was removed so the Irp failed dur to tag change. // // // If this is already a read-tag request then there's no battery present // PopCB.State = PopCB.State == PO_CB_READ_TAG ? PO_CB_WAIT_TAG : PO_CB_READ_TAG; PoPrint(PO_BATT, ("PopCB: error %x - new state %d\n", Irp->IoStatus.Status, PopCB.State )); } else { PoPrint(PO_BATT, ("PopCB: irp cancelled\n")); PopRecalculateCBTriggerLevels (PO_TRG_SYSTEM | PO_TRG_USER); } } // // If new state is none, then there's no battery // if (PopCB.State != PO_CB_NONE) { // // Issue new request based on current state // IrpSp = IoGetNextIrpStackLocation(Irp); IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL; IoctlCode = IOCTL_BATTERY_QUERY_INFORMATION; PopCB.u.QueryInfo.BatteryTag = PopCB.Tag; InputBuffer = &PopCB.u.QueryInfo; InputBufferLength = sizeof(PopCB.u.QueryInfo); switch (PopCB.State) { case PO_CB_READ_TAG: PoPrint(PO_BATT, ("PopCB: query tag\n")); IoctlCode = IOCTL_BATTERY_QUERY_TAG; PopCB.u.Tag = (ULONG) 0; InputBufferLength = sizeof(ULONG); OutputBufferLength = sizeof(PopCB.Tag); break; case PO_CB_WAIT_TAG: PoPrint(PO_BATT, ("PopCB: query tag\n")); // // Battery is gone. Wait for it to appear // IoctlCode = IOCTL_BATTERY_QUERY_TAG; PopCB.u.Tag = (ULONG) -1; InputBufferLength = sizeof(ULONG); OutputBufferLength = sizeof(PopCB.Tag); // // Notify battery status change, and wake any threads // PopSetNotificationWork (PO_NOTIFY_BATTERY_STATUS); if (PopCB.ThreadWaiting) { PopCB.ThreadWaiting = FALSE; KeSetEvent (&PopCB.Event, 0, FALSE); } break; case PO_CB_READ_INFO: PoPrint(PO_BATT, ("PopCB: query info\n")); PopCB.u.QueryInfo.InformationLevel = BatteryInformation; OutputBufferLength = sizeof(PopCB.Info); break; case PO_CB_READ_STATUS: // // Calculate next wait // PopCB.u.Wait.BatteryTag = PopCB.Tag; PopCB.u.Wait.PowerState = PopCB.Status.PowerState; PopCB.u.Wait.Timeout = (ULONG) -1; if (PopCB.ThreadWaiting) { PopCB.u.Wait.Timeout = 0; } i = (PopCB.Info.FullChargedCapacity * PopPolicy->BroadcastCapacityResolution) / 100; if (!i) { i = 1; } if (PopCB.Status.Capacity > i) { PopCB.u.Wait.LowCapacity = PopCB.Status.Capacity - i; } else { PopCB.u.Wait.LowCapacity = 0; } PopCB.u.Wait.HighCapacity = PopCB.Status.Capacity + i; if (PopCB.u.Wait.HighCapacity < i) { // avoid rare case of overflow PopCB.u.Wait.HighCapacity = (ULONG) -1; } // // Check limits against power policies // for (i=0; i < PO_NUM_POWER_LEVELS; i++) { if (PopCB.Trigger[i].Flags & PO_TRG_SET) { if (PopCB.Trigger[i].Battery.Level < PopCB.Status.Capacity && PopCB.Trigger[i].Battery.Level > PopCB.u.Wait.LowCapacity) { PopCB.u.Wait.LowCapacity = PopCB.Trigger[i].Battery.Level; } if (PopCB.Trigger[i].Battery.Level > PopCB.Status.Capacity && PopCB.Trigger[i].Battery.Level < PopCB.u.Wait.HighCapacity) { PopCB.u.Wait.HighCapacity = PopCB.Trigger[i].Battery.Level; } } } IoctlCode = IOCTL_BATTERY_QUERY_STATUS; InputBuffer = &PopCB.u.Wait; InputBufferLength = sizeof(PopCB.u.Wait); OutputBufferLength = sizeof(PopCB.Status); PoPrint(PO_BATT, ("PopCB: timeout %x, pwrstate %x, low %x - high %x\n", PopCB.u.Wait.Timeout, PopCB.u.Wait.PowerState, PopCB.u.Wait.LowCapacity, PopCB.u.Wait.HighCapacity )); break; case PO_CB_READ_EST_TIME: PoPrint(PO_BATT, ("PopCB: query est time\n")); PopCB.u.QueryInfo.InformationLevel = BatteryEstimatedTime; PopCB.u.QueryInfo.AtRate = 0; OutputBufferLength = sizeof(PopCB.EstTime); break; default: PopInternalAddToDumpFile( IrpSp, sizeof(IO_STACK_LOCATION), DeviceObject, NULL, NULL, NULL ); KeBugCheckEx( INTERNAL_POWER_ERROR, 0x301, POP_BATT, (ULONG_PTR)DeviceObject, (ULONG_PTR)IrpSp ); break; } // // Submit IRP // IrpSp->Parameters.DeviceIoControl.IoControlCode = IoctlCode; IrpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength; IrpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength; Irp->AssociatedIrp.SystemBuffer = &PopCB.u; Irp->UserBuffer = &PopCB.u; Irp->PendingReturned = FALSE; Irp->Cancel = FALSE; IoSetCompletionRoutine (Irp, PopCompletePolicyIrp, NULL, TRUE, TRUE, TRUE); IoCallDriver (DeviceObject, Irp); } else { // // Battery has disappeared (state is PO_CB_NONE) // PoPrint(PO_BATT, ("PopCB: Battery removed\n")); PopSetNotificationWork (PO_NOTIFY_BATTERY_STATUS); // // Set policy to AC // if (PopPolicy != &PopAcPolicy) { PopPolicy = &PopAcPolicy; PopProcessorPolicy = &PopAcProcessorPolicy; PopSetNotificationWork( PO_NOTIFY_ACDC_CALLBACK | PO_NOTIFY_POLICY | PO_NOTIFY_PROCESSOR_POLICY ); PopApplyThermalThrottle(); PopIdleUpdateIdleHandlers(); PopInitSIdle (); } // // Wake any threads // if (PopCB.ThreadWaiting) { PopCB.ThreadWaiting = FALSE; KeSetEvent (&PopCB.Event, 0, FALSE); } // // Cleanup // IoFreeIrp (Irp); PopCB.StatusIrp = NULL; ObDereferenceObject (DeviceObject); } } VOID PopRecalculateCBTriggerLevels ( ULONG Flags ) /*++ Routine Description: This function is invoked to set the trigger battery levels based on the power policy. This will be invoked whenever the power policy is changed, or whenever there is a battery status change that could affect these settings. N.B. PopPolicyLock must be held. Arguments: Flags- The flags to set if the level has already been passed: example: When user changes alarm leve, we don't want clear PO_TRG_USER|PO_TRG_SYSTEM. If the recalculation was caused by a change (startup, or AC unplug), we just want to set PO_TRG_SYSTEM because we still want the user notification. Return Value: None. --*/ { PSYSTEM_POWER_LEVEL DPolicy; ULONG i; // // Calculate any level settings // for (i=0; i < PO_NUM_POWER_LEVELS; i++) { DPolicy = &PopPolicy->DischargePolicy[i]; // // If this setting not calculated handle it // if (!(PopCB.Trigger[i].Flags & PO_TRG_SET) && DPolicy->Enable) { // // Compute battery capacity setting for percentage // PopCB.Trigger[i].Flags |= PO_TRG_SET; PopCB.Trigger[i].Battery.Level = PopCB.Info.FullChargedCapacity * DPolicy->BatteryLevel / 100 + PopCB.Info.FullChargedCapacity / 200; // // Make sure setting is not below the lowest default // if (PopCB.Trigger[i].Battery.Level < PopCB.Info.DefaultAlert1) { PopCB.Trigger[i].Battery.Level = PopCB.Info.DefaultAlert1; } // // Skip system action if battery capacity was already below level. // This will occur on startup, when a battery is changed, // and when AC comes or goes. // if (PopCB.Status.Capacity < PopCB.Trigger[i].Battery.Level) { PopCB.Trigger[i].Flags |= Flags; } } } } VOID PopComputeCBTime ( VOID ) /*++ Routine Description: This function is invoked after the battery status & estimated time have been read from the battery. This function can apply heuristics or other knowedge to improve the extimated time. N.B. PopPolicyLock must be held. Arguments: None Return Value: None. --*/ { // for now just use the batteries value PopCB.AdjustedEstTime = PopCB.EstTime; } VOID PopResetCBTriggers ( IN UCHAR Flags ) /*++ Routine Description: This function clears the requested bits from the batteries trigger flags. N.B. PopPolicyLock must be held. Arguments: Flags - Bits to clear Return Value: Status --*/ { ULONG i; ASSERT_POLICY_LOCK_OWNED(); // // Clear flag bits // Flags = ~Flags; for (i=0; i < PO_NUM_POWER_LEVELS; i++) { PopCB.Trigger[i].Flags &= Flags; } // // Reread battery status // if (PopCB.StatusIrp) { IoCancelIrp (PopCB.StatusIrp); } } NTSTATUS PopCurrentPowerState ( OUT PSYSTEM_BATTERY_STATE PowerState ) /*++ Routine Description: This function returns the current system power state. If needed, this function will cause the composite battery irp to get the current battery status. N.B. PopPolicyLock must be held. N.B. The function may drop the PopPolicyLock Arguments: PowerState - The current power state Return Value: Status --*/ { ULONGLONG CurrentTime; NTSTATUS Status; ASSERT_POLICY_LOCK_OWNED(); Status = STATUS_SUCCESS; RtlZeroMemory (PowerState, sizeof(SYSTEM_BATTERY_STATE)); // // Wait for valid state in PopCB // do { // // If there's not a composite battery, then return // if (PopCB.State == PO_CB_NONE || PopCB.State == PO_CB_WAIT_TAG) { PowerState->AcOnLine = PopPolicy == &PopAcPolicy; // Indicate no battery found... PERFINFO_POWER_BATTERY_LIFE_INFO(-1, 0); return STATUS_SUCCESS; } // // If device state not being read, we need to wait // if (PopCB.State == PO_CB_READ_STATUS) { // // If last EstTime was calculated within PO_MAX_CB_CACHE_TIME, // use the current data. (note this implies status was sucessfully // read just before time was calcualted) // CurrentTime = KeQueryInterruptTime(); if (CurrentTime - PopCB.EstTimeTime < PO_MAX_CB_CACHE_TIME) { break; } } // // Need new status. If no other threads are waiting for // system power state, then setup for wait // if (!PopCB.ThreadWaiting) { PopCB.ThreadWaiting = TRUE; KeResetEvent (&PopCB.Event); // // If read status is in progress, cancel it so we // can read status now // if (PopCB.State == PO_CB_READ_STATUS) { IoCancelIrp (PopCB.StatusIrp); } } // // Wait for status update // PopReleasePolicyLock (FALSE); Status = KeWaitForSingleObject (&PopCB.Event, Executive, KernelMode, TRUE, NULL); PopAcquirePolicyLock (); } while (NT_SUCCESS(Status)); // // Generate power state // PowerState->AcOnLine = (PopCB.Status.PowerState & BATTERY_POWER_ON_LINE) ? TRUE : FALSE; PowerState->BatteryPresent = TRUE; PowerState->Charging = (PopCB.Status.PowerState & BATTERY_CHARGING) ? TRUE : FALSE; PowerState->Discharging = (PopCB.Status.PowerState & BATTERY_DISCHARGING) ? TRUE : FALSE; PowerState->MaxCapacity = PopCB.Info.FullChargedCapacity; PowerState->RemainingCapacity = PopCB.Status.Capacity; PowerState->Rate = PopCB.Status.Current; PowerState->EstimatedTime = PopCB.AdjustedEstTime; PowerState->DefaultAlert1 = PopCB.Info.DefaultAlert1; PowerState->DefaultAlert2 = PopCB.Info.DefaultAlert2; PERFINFO_POWER_BATTERY_LIFE_INFO(PowerState->RemainingCapacity, PowerState->Rate); return Status; }