1// Copyright 2020 The Chromium Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/power_monitor/thermal_state_observer_mac.h" 6 7#import <Foundation/Foundation.h> 8#include <IOKit/IOKitLib.h> 9#include <IOKit/pwr_mgt/IOPM.h> 10#include <IOKit/pwr_mgt/IOPMKeys.h> 11#include <IOKit/pwr_mgt/IOPMLib.h> 12#include <IOKit/pwr_mgt/IOPMLibDefs.h> 13#include <notify.h> 14 15#include <memory> 16 17#include "base/apple/scoped_cftyperef.h" 18#include "base/logging.h" 19#include "base/power_monitor/power_monitor.h" 20#include "base/power_monitor/power_monitor_source.h" 21#include "base/power_monitor/power_observer.h" 22 23namespace { 24 25base::PowerThermalObserver::DeviceThermalState 26NSProcessInfoThermalStateToDeviceThermalState( 27 NSProcessInfoThermalState nsinfo_state) { 28 switch (nsinfo_state) { 29 case NSProcessInfoThermalStateNominal: 30 return base::PowerThermalObserver::DeviceThermalState::kNominal; 31 case NSProcessInfoThermalStateFair: 32 return base::PowerThermalObserver::DeviceThermalState::kFair; 33 case NSProcessInfoThermalStateSerious: 34 return base::PowerThermalObserver::DeviceThermalState::kSerious; 35 case NSProcessInfoThermalStateCritical: 36 return base::PowerThermalObserver::DeviceThermalState::kCritical; 37 } 38 NOTREACHED(); 39} 40} 41 42namespace base { 43 44struct ThermalStateObserverMac::ObjCStorage { 45 id __strong thermal_state_update_observer = nil; 46}; 47 48ThermalStateObserverMac::ThermalStateObserverMac( 49 StateUpdateCallback state_update_callback, 50 SpeedLimitUpdateCallback speed_limit_update_callback, 51 const char* power_notification_key) 52 : power_notification_key_(power_notification_key), 53 objc_storage_(std::make_unique<ObjCStorage>()) { 54 auto on_state_change_block = ^(NSNotification* notification) { 55 auto state = PowerThermalObserver::DeviceThermalState::kUnknown; 56 // |thermalState| is basically a scale of power usage and its associated 57 // thermal dissipation increase, from Nominal upwards, see: 58 // https://developer.apple.com/library/archive/documentation/Performance/Conceptual/power_efficiency_guidelines_osx/RespondToThermalStateChanges.html 59 NSProcessInfoThermalState nsinfo_state = 60 NSProcessInfo.processInfo.thermalState; 61 state = NSProcessInfoThermalStateToDeviceThermalState(nsinfo_state); 62 if (state_for_testing_ != 63 PowerThermalObserver::DeviceThermalState::kUnknown) 64 state = state_for_testing_; 65 DVLOG(1) << __func__ << ": " 66 << PowerMonitorSource::DeviceThermalStateToString(state); 67 state_update_callback.Run(state); 68 }; 69 70 objc_storage_->thermal_state_update_observer = 71 [NSNotificationCenter.defaultCenter 72 addObserverForName:NSProcessInfoThermalStateDidChangeNotification 73 object:nil 74 queue:nil 75 usingBlock:on_state_change_block]; 76 77 auto on_speed_change_block = ^() { 78 int speed_limit = GetCurrentSpeedLimit(); 79 DVLOG(1) << __func__ << ": " << speed_limit; 80 speed_limit_update_callback.Run(speed_limit); 81 }; 82 83 uint32_t result = notify_register_dispatch(power_notification_key_, 84 &speed_limit_notification_token_, 85 dispatch_get_main_queue(), ^(int) { 86 on_speed_change_block(); 87 }); 88 LOG_IF(ERROR, result != NOTIFY_STATUS_OK) 89 << __func__ 90 << " unable to register to power notifications. Result: " << result; 91 92 // Force a first call to grab the current status. 93 on_state_change_block(nil); 94 on_speed_change_block(); 95} 96 97ThermalStateObserverMac::~ThermalStateObserverMac() { 98 [NSNotificationCenter.defaultCenter 99 removeObserver:objc_storage_->thermal_state_update_observer]; 100 notify_cancel(speed_limit_notification_token_); 101} 102 103PowerThermalObserver::DeviceThermalState 104ThermalStateObserverMac::GetCurrentThermalState() { 105 if (state_for_testing_ != PowerThermalObserver::DeviceThermalState::kUnknown) 106 return state_for_testing_; 107 NSProcessInfoThermalState nsinfo_state = 108 NSProcessInfo.processInfo.thermalState; 109 return NSProcessInfoThermalStateToDeviceThermalState(nsinfo_state); 110} 111 112int ThermalStateObserverMac::GetCurrentSpeedLimit() const { 113 apple::ScopedCFTypeRef<CFDictionaryRef> dictionary; 114 IOReturn result = IOPMCopyCPUPowerStatus(dictionary.InitializeInto()); 115 if (result != kIOReturnSuccess) { 116 DVLOG(1) << __func__ 117 << "Unable to get CPU power status, result = " << result; 118 return PowerThermalObserver::kSpeedLimitMax; 119 } 120 if (CFTypeRef value = CFDictionaryGetValue( 121 dictionary.get(), CFSTR(kIOPMCPUPowerLimitProcessorSpeedKey))) { 122 int speed_limit = -1; 123 if (CFNumberGetValue(reinterpret_cast<CFNumberRef>(value), kCFNumberIntType, 124 &speed_limit)) { 125 return speed_limit; 126 } else { 127 DVLOG(1) << __func__ << "Unable to get speed limit value"; 128 } 129 } else { 130 DVLOG(1) << __func__ << "Unable to get speed limit"; 131 } 132 return PowerThermalObserver::kSpeedLimitMax; 133} 134} 135