3
* VirtualBox interface to host's power notification service
7
* Copyright (C) 2008 Oracle Corporation
9
* This file is part of VirtualBox Open Source Edition (OSE), as
10
* available from http://www.virtualbox.org. This file is free software;
11
* you can redistribute it and/or modify it under the terms of the GNU
12
* General Public License (GPL) as published by the Free Software
13
* Foundation, in version 2 as it comes in the "COPYING" file of the
14
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18
#include "HostPower.h"
21
#include <IOKit/IOMessage.h>
22
#include <IOKit/ps/IOPowerSources.h>
23
#include <IOKit/ps/IOPSKeys.h>
25
#define POWER_SOURCE_OUTLET 1
26
#define POWER_SOURCE_BATTERY 2
28
HostPowerServiceDarwin::HostPowerServiceDarwin (VirtualBox *aVirtualBox)
29
: HostPowerService (aVirtualBox)
31
, mRootPort (MACH_PORT_NULL)
36
/* Create the new worker thread. */
37
int rc = RTThreadCreate (&mThread, HostPowerServiceDarwin::powerChangeNotificationThread, this, 65536,
38
RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "MainPower");
41
LogFlow (("RTThreadCreate failed with %Rrc\n", rc));
44
HostPowerServiceDarwin::~HostPowerServiceDarwin()
46
/* Jump out of the run loop. */
47
CFRunLoopStop (mRunLoop);
48
/* Remove the sleep notification port from the application runloop. */
49
CFRunLoopRemoveSource (CFRunLoopGetCurrent(),
50
IONotificationPortGetRunLoopSource (mNotifyPort),
51
kCFRunLoopCommonModes);
52
/* Deregister for system sleep notifications. */
53
IODeregisterForSystemPower (&mNotifierObject);
54
/* IORegisterForSystemPower implicitly opens the Root Power Domain
55
* IOService so we close it here. */
56
IOServiceClose (mRootPort);
57
/* Destroy the notification port allocated by IORegisterForSystemPower */
58
IONotificationPortDestroy (mNotifyPort);
62
DECLCALLBACK(int) HostPowerServiceDarwin::powerChangeNotificationThread (RTTHREAD /* ThreadSelf */, void *pInstance)
64
HostPowerServiceDarwin *pPowerObj = static_cast<HostPowerServiceDarwin *> (pInstance);
66
/* We have to initial set the critical state of the battery, cause we want
67
* not the HostPowerService to inform about that state when a VM starts.
68
* See lowPowerHandler for more info. */
69
pPowerObj->checkBatteryCriticalLevel();
71
/* Register to receive system sleep notifications */
72
pPowerObj->mRootPort = IORegisterForSystemPower (pPowerObj, &pPowerObj->mNotifyPort,
73
HostPowerServiceDarwin::powerChangeNotificationHandler,
74
&pPowerObj->mNotifierObject);
75
if (pPowerObj->mRootPort == MACH_PORT_NULL)
77
LogFlow (("IORegisterForSystemPower failed\n"));
78
return VERR_NOT_SUPPORTED;
80
pPowerObj->mRunLoop = CFRunLoopGetCurrent();
81
/* Add the notification port to the application runloop */
82
CFRunLoopAddSource (pPowerObj->mRunLoop,
83
IONotificationPortGetRunLoopSource (pPowerObj->mNotifyPort),
84
kCFRunLoopCommonModes);
86
/* Register for all battery change events. The handler will check for low
87
* power events themself. */
88
CFRunLoopSourceRef runLoopSource = IOPSNotificationCreateRunLoopSource(HostPowerServiceDarwin::lowPowerHandler,
90
CFRunLoopAddSource(pPowerObj->mRunLoop,
92
kCFRunLoopCommonModes);
94
/* Start the run loop. This blocks. */
99
void HostPowerServiceDarwin::powerChangeNotificationHandler (void *pvData, io_service_t /* service */, natural_t messageType, void *pMessageArgument)
101
HostPowerServiceDarwin *pPowerObj = static_cast<HostPowerServiceDarwin *> (pvData);
102
Log (( "powerChangeNotificationHandler: messageType %08lx, arg %08lx\n", (long unsigned int)messageType, (long unsigned int)pMessageArgument));
106
case kIOMessageCanSystemSleep:
108
/* Idle sleep is about to kick in. This message will not be
109
* sent for forced sleep. Applications have a chance to prevent
110
* sleep by calling IOCancelPowerChange. Most applications
111
* should not prevent idle sleep. Power Management waits up to
112
* 30 seconds for you to either allow or deny idle sleep. If
113
* you don't acknowledge this power change by calling either
114
* IOAllowPowerChange or IOCancelPowerChange, the system will
115
* wait 30 seconds then go to sleep. */
116
IOAllowPowerChange (pPowerObj->mRootPort, reinterpret_cast<long> (pMessageArgument));
119
case kIOMessageSystemWillSleep:
121
/* The system will go for sleep. */
122
pPowerObj->notify (HostPowerEvent_Suspend);
123
/* If you do not call IOAllowPowerChange or IOCancelPowerChange to
124
* acknowledge this message, sleep will be delayed by 30 seconds.
125
* NOTE: If you call IOCancelPowerChange to deny sleep it returns
126
* kIOReturnSuccess, however the system WILL still go to sleep. */
127
IOAllowPowerChange (pPowerObj->mRootPort, reinterpret_cast<long> (pMessageArgument));
130
case kIOMessageSystemWillPowerOn:
132
/* System has started the wake up process. */
135
case kIOMessageSystemHasPoweredOn:
137
/* System has finished the wake up process. */
138
pPowerObj->notify (HostPowerEvent_Resume);
146
void HostPowerServiceDarwin::lowPowerHandler (void *pvData)
148
HostPowerServiceDarwin *pPowerObj = static_cast<HostPowerServiceDarwin *> (pvData);
150
/* Following role for sending the BatteryLow event (5% is critical):
151
* - Not at VM start even if the battery is in an critical state already.
152
* - When the power cord is removed so the power supply change from AC to
153
* battery & the battery is in an critical state nothing is triggered.
154
* This has to be discussed.
155
* - When the power supply is the battery & the state of the battery level
156
* changed from normal to critical. The state transition from critical to
157
* normal triggers nothing. */
158
bool fCriticalStateChanged = false;
159
pPowerObj->checkBatteryCriticalLevel (&fCriticalStateChanged);
160
if (fCriticalStateChanged)
161
pPowerObj->notify (HostPowerEvent_BatteryLow);
164
void HostPowerServiceDarwin::checkBatteryCriticalLevel (bool *pfCriticalChanged)
166
CFTypeRef pBlob = IOPSCopyPowerSourcesInfo();
167
CFArrayRef pSources = IOPSCopyPowerSourcesList (pBlob);
169
CFDictionaryRef pSource = NULL;
172
int powerSource = POWER_SOURCE_OUTLET;
173
bool critical = false;
175
if (CFArrayGetCount (pSources) > 0)
177
for (int i = 0; i < CFArrayGetCount (pSources); ++i)
179
pSource = IOPSGetPowerSourceDescription (pBlob, CFArrayGetValueAtIndex (pSources, i));
180
/* If the source is empty skip over to the next one. */
183
/* Skip all power sources which are currently not present like a
185
if (CFDictionaryGetValue (pSource, CFSTR (kIOPSIsPresentKey)) == kCFBooleanFalse)
187
/* Only internal power types are of interest. */
188
result = CFDictionaryGetValueIfPresent (pSource, CFSTR (kIOPSTransportTypeKey), &psValue);
190
CFStringCompare ((CFStringRef)psValue, CFSTR (kIOPSInternalType), 0) == kCFCompareEqualTo)
192
/* First check which power source we are connect on. */
193
result = CFDictionaryGetValueIfPresent (pSource, CFSTR (kIOPSPowerSourceStateKey), &psValue);
195
CFStringCompare ((CFStringRef)psValue, CFSTR (kIOPSACPowerValue), 0) == kCFCompareEqualTo)
196
powerSource = POWER_SOURCE_OUTLET;
198
CFStringCompare ((CFStringRef)psValue, CFSTR (kIOPSBatteryPowerValue), 0) == kCFCompareEqualTo)
199
powerSource = POWER_SOURCE_BATTERY;
203
float remCapacity = 0.0f;
205
/* Fetch the current capacity value of the power source */
206
result = CFDictionaryGetValueIfPresent (pSource, CFSTR (kIOPSCurrentCapacityKey), &psValue);
208
CFNumberGetValue ((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);
209
/* Fetch the maximum capacity value of the power source */
210
result = CFDictionaryGetValueIfPresent (pSource, CFSTR (kIOPSMaxCapacityKey), &psValue);
212
CFNumberGetValue ((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);
214
/* Calculate the remaining capacity in percent */
215
remCapacity = ((float)curCapacity/(float)maxCapacity * 100.0);
217
/* Check for critical. 5 percent is default. */
218
int criticalValue = 5;
219
result = CFDictionaryGetValueIfPresent (pSource, CFSTR (kIOPSDeadWarnLevelKey), &psValue);
221
CFNumberGetValue ((CFNumberRef)psValue, kCFNumberSInt32Type, &criticalValue);
222
critical = (remCapacity < criticalValue);
223
/* We have to take action only if we are on battery, the
224
* previous state wasn't critical, the state has changed & the
225
* user requested that info. */
226
if (powerSource == POWER_SOURCE_BATTERY &&
227
mCritical == false &&
228
mCritical != critical &&
230
*pfCriticalChanged = true;
231
Log (("checkBatteryCriticalLevel: Remains: %d.%d%% Critical: %d Critical State Changed: %d\n", (int)remCapacity, (int)(remCapacity * 10) % 10, critical, pfCriticalChanged?*pfCriticalChanged:-1));
235
/* Save the new state */
236
mCritical = critical;
239
CFRelease (pSources);