// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/power_monitor/thermal_state_observer_mac.h"
#import <Foundation/Foundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/pwr_mgt/IOPM.h>
#include <IOKit/pwr_mgt/IOPMKeys.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/pwr_mgt/IOPMLibDefs.h>
#include <notify.h>
#include <memory>
#include "base/apple/scoped_cftyperef.h"
#include "base/logging.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_source.h"
#include "base/power_monitor/power_observer.h"
namespace {
base::PowerThermalObserver::DeviceThermalState
NSProcessInfoThermalStateToDeviceThermalState(
NSProcessInfoThermalState nsinfo_state) {
switch (nsinfo_state) {
case NSProcessInfoThermalStateNominal:
return base::PowerThermalObserver::DeviceThermalState::kNominal;
case NSProcessInfoThermalStateFair:
return base::PowerThermalObserver::DeviceThermalState::kFair;
case NSProcessInfoThermalStateSerious:
return base::PowerThermalObserver::DeviceThermalState::kSerious;
case NSProcessInfoThermalStateCritical:
return base::PowerThermalObserver::DeviceThermalState::kCritical;
}
NOTREACHED();
}
}
namespace base {
struct ThermalStateObserverMac::ObjCStorage {
id __strong thermal_state_update_observer = nil;
};
ThermalStateObserverMac::ThermalStateObserverMac(
StateUpdateCallback state_update_callback,
SpeedLimitUpdateCallback speed_limit_update_callback,
const char* power_notification_key)
: power_notification_key_(power_notification_key),
objc_storage_(std::make_unique<ObjCStorage>()) {
auto on_state_change_block = ^(NSNotification* notification) {
auto state = PowerThermalObserver::DeviceThermalState::kUnknown;
// |thermalState| is basically a scale of power usage and its associated
// thermal dissipation increase, from Nominal upwards, see:
// https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html
NSProcessInfoThermalState nsinfo_state =
NSProcessInfo.processInfo.thermalState;
state = NSProcessInfoThermalStateToDeviceThermalState(nsinfo_state);
if (state_for_testing_ !=
PowerThermalObserver::DeviceThermalState::kUnknown)
state = state_for_testing_;
DVLOG(1) << __func__ << ": "
<< PowerMonitorSource::DeviceThermalStateToString(state);
state_update_callback.Run(state);
};
objc_storage_->thermal_state_update_observer =
[NSNotificationCenter.defaultCenter
addObserverForName:NSProcessInfoThermalStateDidChangeNotification
object:nil
queue:nil
usingBlock:on_state_change_block];
auto on_speed_change_block = ^() {
int speed_limit = GetCurrentSpeedLimit();
DVLOG(1) << __func__ << ": " << speed_limit;
speed_limit_update_callback.Run(speed_limit);
};
uint32_t result = notify_register_dispatch(power_notification_key_,
&speed_limit_notification_token_,
dispatch_get_main_queue(), ^(int) {
on_speed_change_block();
});
LOG_IF(ERROR, result != NOTIFY_STATUS_OK)
<< __func__
<< " unable to register to power notifications. Result: " << result;
// Force a first call to grab the current status.
on_state_change_block(nil);
on_speed_change_block();
}
ThermalStateObserverMac::~ThermalStateObserverMac() {
[NSNotificationCenter.defaultCenter
removeObserver:objc_storage_->thermal_state_update_observer];
notify_cancel(speed_limit_notification_token_);
}
PowerThermalObserver::DeviceThermalState
ThermalStateObserverMac::GetCurrentThermalState() {
if (state_for_testing_ != PowerThermalObserver::DeviceThermalState::kUnknown)
return state_for_testing_;
NSProcessInfoThermalState nsinfo_state =
NSProcessInfo.processInfo.thermalState;
return NSProcessInfoThermalStateToDeviceThermalState(nsinfo_state);
}
int ThermalStateObserverMac::GetCurrentSpeedLimit() {
apple::ScopedCFTypeRef<CFDictionaryRef> dictionary;
IOReturn result = IOPMCopyCPUPowerStatus(dictionary.InitializeInto());
if (result != kIOReturnSuccess) {
DVLOG(1) << __func__
<< "Unable to get CPU power status, result = " << result;
return PowerThermalObserver::kSpeedLimitMax;
}
if (CFTypeRef value = CFDictionaryGetValue(
dictionary.get(), CFSTR(kIOPMCPUPowerLimitProcessorSpeedKey))) {
int speed_limit = -1;
if (CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberIntType,
&speed_limit)) {
return speed_limit;
} else {
DVLOG(1) << __func__ << "Unable to get speed limit value";
}
} else {
DVLOG(1) << __func__ << "Unable to get speed limit";
}
return PowerThermalObserver::kSpeedLimitMax;
}
}