• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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