// 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 #include #include #include #include #include #include #include #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()) { 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() const { apple::ScopedCFTypeRef 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(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; } }