aidl: Import Pixel Thermal HAL aidl implementation

* From hardware/google/pixel @ android-14.0.0_r15

Change-Id: I30e35c8e4ef58956f849d64e184aa7e37ec67ef9
urubino
Tim Zimmermann 7 months ago
parent 7e2c8c3a2b
commit 96d8907514
No known key found for this signature in database
GPG Key ID: 6DC21A63F819C5EF
  1. 78
      aidl/thermal/Android.bp
  2. 793
      aidl/thermal/Thermal.cpp
  3. 120
      aidl/thermal/Thermal.h
  4. 14
      aidl/thermal/android.hardware.thermal-service.pixel.rc
  5. 7
      aidl/thermal/android.hardware.thermal-service.pixel.xml
  6. 25
      aidl/thermal/init.thermal.logging.sh
  7. 13
      aidl/thermal/init.thermal.symlinks.sh
  8. 130
      aidl/thermal/pixel-thermal-logd.rc
  9. 11
      aidl/thermal/pixel-thermal-symlinks.rc
  10. 51
      aidl/thermal/service.cpp
  11. 1180
      aidl/thermal/thermal-helper.cpp
  12. 196
      aidl/thermal/thermal-helper.h
  13. 219
      aidl/thermal/utils/config_schema.json
  14. 342
      aidl/thermal/utils/power_files.cpp
  15. 95
      aidl/thermal/utils/power_files.h
  16. 138
      aidl/thermal/utils/powerhal_helper.cpp
  17. 63
      aidl/thermal/utils/powerhal_helper.h
  18. 88
      aidl/thermal/utils/thermal_files.cpp
  19. 53
      aidl/thermal/utils/thermal_files.h
  20. 1184
      aidl/thermal/utils/thermal_info.cpp
  21. 205
      aidl/thermal/utils/thermal_info.h
  22. 509
      aidl/thermal/utils/thermal_stats_helper.cpp
  23. 170
      aidl/thermal/utils/thermal_stats_helper.h
  24. 767
      aidl/thermal/utils/thermal_throttling.cpp
  25. 142
      aidl/thermal/utils/thermal_throttling.h
  26. 533
      aidl/thermal/utils/thermal_watcher.cpp
  27. 114
      aidl/thermal/utils/thermal_watcher.h

@ -0,0 +1,78 @@
cc_binary {
name: "android.hardware.thermal-service.pixel",
srcs: [
"service.cpp",
"Thermal.cpp",
"thermal-helper.cpp",
"utils/thermal_throttling.cpp",
"utils/thermal_info.cpp",
"utils/thermal_files.cpp",
"utils/power_files.cpp",
"utils/powerhal_helper.cpp",
"utils/thermal_stats_helper.cpp",
"utils/thermal_watcher.cpp",
],
vendor: true,
relative_install_path: "hw",
vintf_fragments: [
"android.hardware.thermal-service.pixel.xml"
],
init_rc: [
"android.hardware.thermal-service.pixel.rc",
],
shared_libs: [
"libbase",
"libcutils",
"libjsoncpp",
"libutils",
"libnl",
"libbinder_ndk",
"android.frameworks.stats-V1-ndk",
"android.hardware.power-V1-ndk",
"android.hardware.thermal-V1-ndk",
"pixel-power-ext-V1-ndk",
"pixelatoms-cpp",
],
static_libs: [
"libpixelstats",
],
export_shared_lib_headers: [
"android.frameworks.stats-V1-ndk",
"pixelatoms-cpp",
],
cflags: [
"-Wall",
"-Werror",
"-Wextra",
"-Wunused",
],
tidy: true,
tidy_checks: [
"android-*",
"cert-*",
"clang-analyzer-security*",
],
tidy_checks_as_errors: [
"android-*",
"clang-analyzer-security*",
"cert-*",
],
}
sh_binary {
name: "thermal_logd",
src: "init.thermal.logging.sh",
vendor: true,
init_rc: [
"pixel-thermal-logd.rc",
],
}
sh_binary {
name: "thermal_symlinks",
src: "init.thermal.symlinks.sh",
vendor: true,
init_rc: [
"pixel-thermal-symlinks.rc",
],
}

@ -0,0 +1,793 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
#include "Thermal.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <utils/Trace.h>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
namespace {
ndk::ScopedAStatus initErrorStatus() {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE,
"ThermalHAL not initialized properly.");
}
ndk::ScopedAStatus readErrorStatus() {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
EX_ILLEGAL_STATE, "ThermalHal cannot read any sensor data");
}
bool interfacesEqual(const std::shared_ptr<::ndk::ICInterface> left,
const std::shared_ptr<::ndk::ICInterface> right) {
if (left == nullptr || right == nullptr || !left->isRemote() || !right->isRemote()) {
return left == right;
}
return left->asBinder() == right->asBinder();
}
} // namespace
Thermal::Thermal()
: thermal_helper_(
std::bind(&Thermal::sendThermalChangedCallback, this, std::placeholders::_1)) {}
ndk::ScopedAStatus Thermal::getTemperatures(std::vector<Temperature> *_aidl_return) {
return getFilteredTemperatures(false, TemperatureType::UNKNOWN, _aidl_return);
}
ndk::ScopedAStatus Thermal::getTemperaturesWithType(TemperatureType type,
std::vector<Temperature> *_aidl_return) {
return getFilteredTemperatures(true, type, _aidl_return);
}
ndk::ScopedAStatus Thermal::getFilteredTemperatures(bool filterType, TemperatureType type,
std::vector<Temperature> *_aidl_return) {
*_aidl_return = {};
if (!thermal_helper_.isInitializedOk()) {
return initErrorStatus();
}
if (!thermal_helper_.fillCurrentTemperatures(filterType, false, type, _aidl_return)) {
return readErrorStatus();
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Thermal::getCoolingDevices(std::vector<CoolingDevice> *_aidl_return) {
return getFilteredCoolingDevices(false, CoolingType::BATTERY, _aidl_return);
}
ndk::ScopedAStatus Thermal::getCoolingDevicesWithType(CoolingType type,
std::vector<CoolingDevice> *_aidl_return) {
return getFilteredCoolingDevices(true, type, _aidl_return);
}
ndk::ScopedAStatus Thermal::getFilteredCoolingDevices(bool filterType, CoolingType type,
std::vector<CoolingDevice> *_aidl_return) {
*_aidl_return = {};
if (!thermal_helper_.isInitializedOk()) {
return initErrorStatus();
}
if (!thermal_helper_.fillCurrentCoolingDevices(filterType, type, _aidl_return)) {
return readErrorStatus();
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Thermal::getTemperatureThresholds(
std::vector<TemperatureThreshold> *_aidl_return) {
*_aidl_return = {};
return getFilteredTemperatureThresholds(false, TemperatureType::UNKNOWN, _aidl_return);
}
ndk::ScopedAStatus Thermal::getTemperatureThresholdsWithType(
TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return) {
return getFilteredTemperatureThresholds(true, type, _aidl_return);
}
ndk::ScopedAStatus Thermal::getFilteredTemperatureThresholds(
bool filterType, TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return) {
*_aidl_return = {};
if (!thermal_helper_.isInitializedOk()) {
return initErrorStatus();
}
if (!thermal_helper_.fillTemperatureThresholds(filterType, type, _aidl_return)) {
return readErrorStatus();
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Thermal::registerThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback> &callback) {
ATRACE_CALL();
return registerThermalChangedCallback(callback, false, TemperatureType::UNKNOWN);
}
ndk::ScopedAStatus Thermal::registerThermalChangedCallbackWithType(
const std::shared_ptr<IThermalChangedCallback> &callback, TemperatureType type) {
ATRACE_CALL();
return registerThermalChangedCallback(callback, true, type);
}
ndk::ScopedAStatus Thermal::unregisterThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback> &callback) {
if (callback == nullptr) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Invalid nullptr callback");
}
bool removed = false;
std::lock_guard<std::mutex> _lock(thermal_callback_mutex_);
callbacks_.erase(
std::remove_if(
callbacks_.begin(), callbacks_.end(),
[&](const CallbackSetting &c) {
if (interfacesEqual(c.callback, callback)) {
LOG(INFO)
<< "a callback has been unregistered to ThermalHAL, isFilter: "
<< c.is_filter_type << " Type: " << toString(c.type);
removed = true;
return true;
}
return false;
}),
callbacks_.end());
if (!removed) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Callback wasn't registered");
}
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus Thermal::registerThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback> &callback, bool filterType,
TemperatureType type) {
ATRACE_CALL();
if (callback == nullptr) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Invalid nullptr callback");
}
if (!thermal_helper_.isInitializedOk()) {
return initErrorStatus();
}
std::lock_guard<std::mutex> _lock(thermal_callback_mutex_);
if (std::any_of(callbacks_.begin(), callbacks_.end(), [&](const CallbackSetting &c) {
return interfacesEqual(c.callback, callback);
})) {
return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
"Callback already registered");
}
auto c = callbacks_.emplace_back(callback, filterType, type);
LOG(INFO) << "a callback has been registered to ThermalHAL, isFilter: " << c.is_filter_type
<< " Type: " << toString(c.type);
// Send notification right away after successful thermal callback registration
std::function<void()> handler = [this, c, filterType, type]() {
std::vector<Temperature> temperatures;
if (thermal_helper_.fillCurrentTemperatures(filterType, true, type, &temperatures)) {
for (const auto &t : temperatures) {
if (!filterType || t.type == type) {
LOG(INFO) << "Sending notification: "
<< " Type: " << toString(t.type) << " Name: " << t.name
<< " CurrentValue: " << t.value
<< " ThrottlingStatus: " << toString(t.throttlingStatus);
c.callback->notifyThrottling(t);
}
}
}
};
looper_.addEvent(Looper::Event{handler});
return ndk::ScopedAStatus::ok();
}
void Thermal::sendThermalChangedCallback(const Temperature &t) {
ATRACE_CALL();
std::lock_guard<std::mutex> _lock(thermal_callback_mutex_);
LOG(VERBOSE) << "Sending notification: "
<< " Type: " << toString(t.type) << " Name: " << t.name
<< " CurrentValue: " << t.value
<< " ThrottlingStatus: " << toString(t.throttlingStatus);
callbacks_.erase(std::remove_if(callbacks_.begin(), callbacks_.end(),
[&](const CallbackSetting &c) {
if (!c.is_filter_type || t.type == c.type) {
::ndk::ScopedAStatus ret =
c.callback->notifyThrottling(t);
if (!ret.isOk()) {
LOG(ERROR) << "a Thermal callback is dead, removed "
"from callback list.";
return true;
}
return false;
}
return false;
}),
callbacks_.end());
}
void Thermal::dumpVirtualSensorInfo(std::ostringstream *dump_buf) {
*dump_buf << "getVirtualSensorInfo:" << std::endl;
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &sensor_info_pair : map) {
if (sensor_info_pair.second.virtual_sensor_info != nullptr) {
*dump_buf << " Name: " << sensor_info_pair.first << std::endl;
*dump_buf << " LinkedSensorName: [";
for (size_t i = 0;
i < sensor_info_pair.second.virtual_sensor_info->linked_sensors.size(); i++) {
*dump_buf << sensor_info_pair.second.virtual_sensor_info->linked_sensors[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " LinkedSensorCoefficient: [";
for (size_t i = 0; i < sensor_info_pair.second.virtual_sensor_info->coefficients.size();
i++) {
*dump_buf << sensor_info_pair.second.virtual_sensor_info->coefficients[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " Offset: " << sensor_info_pair.second.virtual_sensor_info->offset
<< std::endl;
*dump_buf << " Trigger Sensor: ";
if (sensor_info_pair.second.virtual_sensor_info->trigger_sensors.empty()) {
*dump_buf << "N/A" << std::endl;
} else {
for (size_t i = 0;
i < sensor_info_pair.second.virtual_sensor_info->trigger_sensors.size(); i++) {
*dump_buf << sensor_info_pair.second.virtual_sensor_info->trigger_sensors[i]
<< " ";
}
*dump_buf << std::endl;
}
*dump_buf << " Formula: ";
switch (sensor_info_pair.second.virtual_sensor_info->formula) {
case FormulaOption::COUNT_THRESHOLD:
*dump_buf << "COUNT_THRESHOLD";
break;
case FormulaOption::WEIGHTED_AVG:
*dump_buf << "WEIGHTED_AVG";
break;
case FormulaOption::MAXIMUM:
*dump_buf << "MAXIMUM";
break;
case FormulaOption::MINIMUM:
*dump_buf << "MINIMUM";
break;
default:
*dump_buf << "NONE";
break;
}
*dump_buf << std::endl;
}
}
}
void Thermal::dumpThrottlingInfo(std::ostringstream *dump_buf) {
*dump_buf << "getThrottlingInfo:" << std::endl;
const auto &map = thermal_helper_.GetSensorInfoMap();
const auto &thermal_throttling_status_map = thermal_helper_.GetThermalThrottlingStatusMap();
for (const auto &name_info_pair : map) {
if (name_info_pair.second.throttling_info == nullptr) {
continue;
}
if (name_info_pair.second.throttling_info->binded_cdev_info_map.size()) {
if (thermal_throttling_status_map.find(name_info_pair.first) ==
thermal_throttling_status_map.end()) {
continue;
}
*dump_buf << " Name: " << name_info_pair.first << std::endl;
if (thermal_throttling_status_map.at(name_info_pair.first)
.pid_power_budget_map.size()) {
*dump_buf << " PID Info:" << std::endl;
*dump_buf << " K_po: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->k_po[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " K_pu: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->k_pu[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " K_i: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->k_i[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " K_d: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->k_d[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " i_max: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->i_max[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " max_alloc_power: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->max_alloc_power[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " min_alloc_power: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->min_alloc_power[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " s_power: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->s_power[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " i_cutoff: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << name_info_pair.second.throttling_info->i_cutoff[i] << " ";
}
*dump_buf << "]" << std::endl;
}
*dump_buf << " Binded CDEV Info:" << std::endl;
for (const auto &binded_cdev_info_pair :
name_info_pair.second.throttling_info->binded_cdev_info_map) {
*dump_buf << " Cooling device name: " << binded_cdev_info_pair.first << std::endl;
if (thermal_throttling_status_map.at(name_info_pair.first)
.pid_power_budget_map.size()) {
*dump_buf << " WeightForPID: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << binded_cdev_info_pair.second.cdev_weight_for_pid[i] << " ";
}
*dump_buf << "]" << std::endl;
}
*dump_buf << " Ceiling: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << binded_cdev_info_pair.second.cdev_ceiling[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " Hard limit: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << binded_cdev_info_pair.second.limit_info[i] << " ";
}
*dump_buf << "]" << std::endl;
if (!binded_cdev_info_pair.second.power_rail.empty()) {
*dump_buf << " Binded power rail: "
<< binded_cdev_info_pair.second.power_rail << std::endl;
*dump_buf << " Power threshold: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << binded_cdev_info_pair.second.power_thresholds[i] << " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " Floor with PowerLink: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
*dump_buf << binded_cdev_info_pair.second.cdev_floor_with_power_link[i]
<< " ";
}
*dump_buf << "]" << std::endl;
*dump_buf << " Release logic: ";
switch (binded_cdev_info_pair.second.release_logic) {
case ReleaseLogic::INCREASE:
*dump_buf << "INCREASE";
break;
case ReleaseLogic::DECREASE:
*dump_buf << "DECREASE";
break;
case ReleaseLogic::STEPWISE:
*dump_buf << "STEPWISE";
break;
case ReleaseLogic::RELEASE_TO_FLOOR:
*dump_buf << "RELEASE_TO_FLOOR";
break;
default:
*dump_buf << "NONE";
break;
}
*dump_buf << std::endl;
*dump_buf << " high_power_check: " << std::boolalpha
<< binded_cdev_info_pair.second.high_power_check << std::endl;
*dump_buf << " throttling_with_power_link: " << std::boolalpha
<< binded_cdev_info_pair.second.throttling_with_power_link
<< std::endl;
}
}
}
}
}
void Thermal::dumpThrottlingRequestStatus(std::ostringstream *dump_buf) {
const auto &thermal_throttling_status_map = thermal_helper_.GetThermalThrottlingStatusMap();
if (!thermal_throttling_status_map.size()) {
return;
}
*dump_buf << "getThrottlingRequestStatus:" << std::endl;
for (const auto &thermal_throttling_status_pair : thermal_throttling_status_map) {
*dump_buf << " Name: " << thermal_throttling_status_pair.first << std::endl;
if (thermal_throttling_status_pair.second.pid_power_budget_map.size()) {
*dump_buf << " power budget request state" << std::endl;
for (const auto &request_pair :
thermal_throttling_status_pair.second.pid_power_budget_map) {
*dump_buf << " " << request_pair.first << ": " << request_pair.second
<< std::endl;
}
}
if (thermal_throttling_status_pair.second.pid_cdev_request_map.size()) {
*dump_buf << " pid cdev request state" << std::endl;
for (const auto &request_pair :
thermal_throttling_status_pair.second.pid_cdev_request_map) {
*dump_buf << " " << request_pair.first << ": " << request_pair.second
<< std::endl;
}
}
if (thermal_throttling_status_pair.second.hardlimit_cdev_request_map.size()) {
*dump_buf << " hard limit cdev request state" << std::endl;
for (const auto &request_pair :
thermal_throttling_status_pair.second.hardlimit_cdev_request_map) {
*dump_buf << " " << request_pair.first << ": " << request_pair.second
<< std::endl;
}
}
if (thermal_throttling_status_pair.second.throttling_release_map.size()) {
*dump_buf << " cdev release state" << std::endl;
for (const auto &request_pair :
thermal_throttling_status_pair.second.throttling_release_map) {
*dump_buf << " " << request_pair.first << ": " << request_pair.second
<< std::endl;
}
}
if (thermal_throttling_status_pair.second.cdev_status_map.size()) {
*dump_buf << " cdev request state" << std::endl;
for (const auto &request_pair : thermal_throttling_status_pair.second.cdev_status_map) {
*dump_buf << " " << request_pair.first << ": " << request_pair.second
<< std::endl;
}
}
}
}
void Thermal::dumpPowerRailInfo(std::ostringstream *dump_buf) {
const auto &power_rail_info_map = thermal_helper_.GetPowerRailInfoMap();
const auto &power_status_map = thermal_helper_.GetPowerStatusMap();
*dump_buf << "getPowerRailInfo:" << std::endl;
for (const auto &power_rail_pair : power_rail_info_map) {
*dump_buf << " Power Rail: " << power_rail_pair.first << std::endl;
*dump_buf << " Power Sample Count: " << power_rail_pair.second.power_sample_count
<< std::endl;
*dump_buf << " Power Sample Delay: " << power_rail_pair.second.power_sample_delay.count()
<< std::endl;
if (power_status_map.count(power_rail_pair.first)) {
auto power_history = power_status_map.at(power_rail_pair.first).power_history;
*dump_buf << " Last Updated AVG Power: "
<< power_status_map.at(power_rail_pair.first).last_updated_avg_power << " mW"
<< std::endl;
if (power_rail_pair.second.virtual_power_rail_info != nullptr) {
*dump_buf << " Formula=";
switch (power_rail_pair.second.virtual_power_rail_info->formula) {
case FormulaOption::COUNT_THRESHOLD:
*dump_buf << "COUNT_THRESHOLD";
break;
case FormulaOption::WEIGHTED_AVG:
*dump_buf << "WEIGHTED_AVG";
break;
case FormulaOption::MAXIMUM:
*dump_buf << "MAXIMUM";
break;
case FormulaOption::MINIMUM:
*dump_buf << "MINIMUM";
break;
default:
*dump_buf << "NONE";
break;
}
*dump_buf << std::endl;
}
for (size_t i = 0; i < power_history.size(); ++i) {
if (power_rail_pair.second.virtual_power_rail_info != nullptr) {
*dump_buf
<< " Linked power rail "
<< power_rail_pair.second.virtual_power_rail_info->linked_power_rails[i]
<< std::endl;
*dump_buf << " Coefficient="
<< power_rail_pair.second.virtual_power_rail_info->coefficients[i]
<< std::endl;
*dump_buf << " Power Samples: ";
} else {
*dump_buf << " Power Samples: ";
}
while (power_history[i].size() > 0) {
const auto power_sample = power_history[i].front();
power_history[i].pop();
*dump_buf << "(T=" << power_sample.duration
<< ", uWs=" << power_sample.energy_counter << ") ";
}
*dump_buf << std::endl;
}
}
}
}
void Thermal::dumpStatsRecord(std::ostringstream *dump_buf, const StatsRecord &stats_record,
std::string_view line_prefix) {
const auto now = boot_clock::now();
*dump_buf << line_prefix << "Time Since Last Stats Report: "
<< std::chrono::duration_cast<std::chrono::minutes>(
now - stats_record.last_stats_report_time)
.count()
<< " mins" << std::endl;
*dump_buf << line_prefix << "Time in State ms: [";
for (const auto &time_in_state : stats_record.time_in_state_ms) {
*dump_buf << time_in_state.count() << " ";
}
*dump_buf << "]" << std::endl;
}
void Thermal::dumpThermalStats(std::ostringstream *dump_buf) {
*dump_buf << "getThermalStatsInfo:" << std::endl;
*dump_buf << " Sensor Temp Stats Info:" << std::endl;
const auto &sensor_temp_stats_map_ = thermal_helper_.GetSensorTempStatsSnapshot();
const std::string sensor_temp_stats_line_prefix(" ");
for (const auto &sensor_temp_stats_pair : sensor_temp_stats_map_) {
*dump_buf << " Sensor Name: " << sensor_temp_stats_pair.first << std::endl;
const auto &sensor_temp_stats = sensor_temp_stats_pair.second;
*dump_buf << " Max Temp: " << sensor_temp_stats.max_temp << ", TimeStamp: "
<< system_clock::to_time_t(sensor_temp_stats.max_temp_timestamp) << std::endl;
*dump_buf << " Min Temp: " << sensor_temp_stats.min_temp << ", TimeStamp: "
<< system_clock::to_time_t(sensor_temp_stats.min_temp_timestamp) << std::endl;
for (const auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) {
*dump_buf << " Record by Threshold: [";
for (const auto &threshold : stats_by_threshold.thresholds) {
*dump_buf << threshold << " ";
}
*dump_buf << "]" << std::endl;
if (stats_by_threshold.logging_name.has_value()) {
*dump_buf << " Logging Name: " << stats_by_threshold.logging_name.value()
<< std::endl;
}
dumpStatsRecord(dump_buf, stats_by_threshold.stats_record,
sensor_temp_stats_line_prefix);
}
if (sensor_temp_stats.stats_by_default_threshold.has_value()) {
*dump_buf << " Record by Severity:" << std::endl;
dumpStatsRecord(dump_buf, sensor_temp_stats.stats_by_default_threshold.value(),
sensor_temp_stats_line_prefix);
}
}
*dump_buf << " Sensor Cdev Request Stats Info:" << std::endl;
const auto &sensor_cdev_request_stats_map_ =
thermal_helper_.GetSensorCoolingDeviceRequestStatsSnapshot();
const std::string sensor_cdev_request_stats_line_prefix(" ");
for (const auto &sensor_cdev_request_stats_pair : sensor_cdev_request_stats_map_) {
*dump_buf << " Sensor Name: " << sensor_cdev_request_stats_pair.first << std::endl;
for (const auto &cdev_request_stats_pair : sensor_cdev_request_stats_pair.second) {
*dump_buf << " Cooling Device Name: " << cdev_request_stats_pair.first << std::endl;
const auto &request_stats = cdev_request_stats_pair.second;
for (const auto &stats_by_threshold : request_stats.stats_by_custom_threshold) {
*dump_buf << " Record by Threshold: [";
for (const auto &threshold : stats_by_threshold.thresholds) {
*dump_buf << threshold << " ";
}
*dump_buf << "]" << std::endl;
if (stats_by_threshold.logging_name.has_value()) {
*dump_buf << " Logging Name: " << stats_by_threshold.logging_name.value()
<< std::endl;
}
dumpStatsRecord(dump_buf, stats_by_threshold.stats_record,
sensor_cdev_request_stats_line_prefix);
}
if (request_stats.stats_by_default_threshold.has_value()) {
*dump_buf << " Record by All State" << std::endl;
dumpStatsRecord(dump_buf, request_stats.stats_by_default_threshold.value(),
sensor_cdev_request_stats_line_prefix);
}
}
}
}
void Thermal::dumpThermalData(int fd) {
std::ostringstream dump_buf;
if (!thermal_helper_.isInitializedOk()) {
dump_buf << "ThermalHAL not initialized properly." << std::endl;
} else {
const auto &sensor_status_map = thermal_helper_.GetSensorStatusMap();
{
dump_buf << "getCachedTemperatures:" << std::endl;
boot_clock::time_point now = boot_clock::now();
for (const auto &sensor_status_pair : sensor_status_map) {
if ((sensor_status_pair.second.thermal_cached.timestamp) ==
boot_clock::time_point::min()) {
continue;
}
dump_buf << " Name: " << sensor_status_pair.first
<< " CachedValue: " << sensor_status_pair.second.thermal_cached.temp
<< " TimeToCache: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(
now - sensor_status_pair.second.thermal_cached.timestamp)
.count()
<< "ms" << std::endl;
}
}
{
dump_buf << "getEmulTemperatures:" << std::endl;
for (const auto &sensor_status_pair : sensor_status_map) {
if (sensor_status_pair.second.emul_setting == nullptr) {
continue;
}
dump_buf << " Name: " << sensor_status_pair.first
<< " EmulTemp: " << sensor_status_pair.second.emul_setting->emul_temp
<< " EmulSeverity: "
<< sensor_status_pair.second.emul_setting->emul_severity << std::endl;
}
}
{
const auto &map = thermal_helper_.GetSensorInfoMap();
dump_buf << "getCurrentTemperatures:" << std::endl;
Temperature temp_2_0;
for (const auto &name_info_pair : map) {
thermal_helper_.readTemperature(name_info_pair.first, &temp_2_0, nullptr, true);
dump_buf << " Type: " << toString(temp_2_0.type)
<< " Name: " << name_info_pair.first << " CurrentValue: " << temp_2_0.value
<< " ThrottlingStatus: " << toString(temp_2_0.throttlingStatus)
<< std::endl;
}
dump_buf << "getTemperatureThresholds:" << std::endl;
for (const auto &name_info_pair : map) {
if (!name_info_pair.second.is_watch) {
continue;
}
dump_buf << " Type: " << toString(name_info_pair.second.type)
<< " Name: " << name_info_pair.first;
dump_buf << " hotThrottlingThreshold: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
dump_buf << name_info_pair.second.hot_thresholds[i] << " ";
}
dump_buf << "] coldThrottlingThreshold: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
dump_buf << name_info_pair.second.cold_thresholds[i] << " ";
}
dump_buf << "] vrThrottlingThreshold: " << name_info_pair.second.vr_threshold;
dump_buf << std::endl;
}
dump_buf << "getHysteresis:" << std::endl;
for (const auto &name_info_pair : map) {
if (!name_info_pair.second.is_watch) {
continue;
}
dump_buf << " Name: " << name_info_pair.first;
dump_buf << " hotHysteresis: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
dump_buf << name_info_pair.second.hot_hysteresis[i] << " ";
}
dump_buf << "] coldHysteresis: [";
for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
dump_buf << name_info_pair.second.cold_hysteresis[i] << " ";
}
dump_buf << "]" << std::endl;
}
}
{
dump_buf << "getCurrentCoolingDevices:" << std::endl;
std::vector<CoolingDevice> cooling_devices;
if (!thermal_helper_.fillCurrentCoolingDevices(false, CoolingType::CPU,
&cooling_devices)) {
dump_buf << " Failed to getCurrentCoolingDevices." << std::endl;
}
for (const auto &c : cooling_devices) {
dump_buf << " Type: " << toString(c.type) << " Name: " << c.name
<< " CurrentValue: " << c.value << std::endl;
}
}
{
dump_buf << "getCallbacks:" << std::endl;
dump_buf << " Total: " << callbacks_.size() << std::endl;
for (const auto &c : callbacks_) {
dump_buf << " IsFilter: " << c.is_filter_type << " Type: " << toString(c.type)
<< std::endl;
}
}
{
dump_buf << "sendCallback:" << std::endl;
dump_buf << " Enabled List: ";
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &name_info_pair : map) {
if (name_info_pair.second.send_cb) {
dump_buf << name_info_pair.first << " ";
}
}
dump_buf << std::endl;
}
{
dump_buf << "sendPowerHint:" << std::endl;
dump_buf << " Enabled List: ";
const auto &map = thermal_helper_.GetSensorInfoMap();
for (const auto &name_info_pair : map) {
if (name_info_pair.second.send_powerhint) {
dump_buf << name_info_pair.first << " ";
}
}
dump_buf << std::endl;
}
dumpVirtualSensorInfo(&dump_buf);
dumpThrottlingInfo(&dump_buf);
dumpThrottlingRequestStatus(&dump_buf);
dumpPowerRailInfo(&dump_buf);
dumpThermalStats(&dump_buf);
{
dump_buf << "getAIDLPowerHalInfo:" << std::endl;
dump_buf << " Exist: " << std::boolalpha << thermal_helper_.isAidlPowerHalExist()
<< std::endl;
dump_buf << " Connected: " << std::boolalpha << thermal_helper_.isPowerHalConnected()
<< std::endl;
dump_buf << " Ext connected: " << std::boolalpha
<< thermal_helper_.isPowerHalExtConnected() << std::endl;
}
}
std::string buf = dump_buf.str();
if (!::android::base::WriteStringToFd(buf, fd)) {
PLOG(ERROR) << "Failed to dump state to fd";
}
fsync(fd);
}
binder_status_t Thermal::dump(int fd, const char **args, uint32_t numArgs) {
if (numArgs == 0 || std::string(args[0]) == "-a") {
dumpThermalData(fd);
return STATUS_OK;
}
if (std::string(args[0]) == "emul_temp") {
return (numArgs != 3 || !thermal_helper_.emulTemp(std::string(args[1]), std::atof(args[2])))
? STATUS_BAD_VALUE
: STATUS_OK;
} else if (std::string(args[0]) == "emul_severity") {
return (numArgs != 3 ||
!thermal_helper_.emulSeverity(std::string(args[1]), std::atoi(args[2])))
? STATUS_BAD_VALUE
: STATUS_OK;
} else if (std::string(args[0]) == "emul_clear") {
return (numArgs != 2 || !thermal_helper_.emulClear(std::string(args[1]))) ? STATUS_BAD_VALUE
: STATUS_OK;
}
return STATUS_BAD_VALUE;
}
void Thermal::Looper::addEvent(const Thermal::Looper::Event &e) {
std::unique_lock<std::mutex> lock(mutex_);
events_.push(e);
cv_.notify_all();
}
void Thermal::Looper::loop() {
while (true) {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&] { return !events_.empty(); });
Event event = events_.front();
events_.pop();
lock.unlock();
event.handler();
}
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,120 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/hardware/thermal/BnThermal.h>
#include <mutex>
#include <thread>
#include "thermal-helper.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
struct CallbackSetting {
CallbackSetting(std::shared_ptr<IThermalChangedCallback> callback, bool is_filter_type,
TemperatureType type)
: callback(std::move(callback)), is_filter_type(is_filter_type), type(type) {}
std::shared_ptr<IThermalChangedCallback> callback;
bool is_filter_type;
TemperatureType type;
};
class Thermal : public BnThermal {
public:
Thermal();
~Thermal() = default;
ndk::ScopedAStatus getTemperatures(std::vector<Temperature> *_aidl_return) override;
ndk::ScopedAStatus getTemperaturesWithType(TemperatureType type,
std::vector<Temperature> *_aidl_return) override;
ndk::ScopedAStatus getCoolingDevices(std::vector<CoolingDevice> *_aidl_return) override;
ndk::ScopedAStatus getCoolingDevicesWithType(CoolingType type,
std::vector<CoolingDevice> *_aidl_return) override;
ndk::ScopedAStatus getTemperatureThresholds(
std::vector<TemperatureThreshold> *_aidl_return) override;
ndk::ScopedAStatus getTemperatureThresholdsWithType(
TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return) override;
ndk::ScopedAStatus registerThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback> &callback) override;
ndk::ScopedAStatus registerThermalChangedCallbackWithType(
const std::shared_ptr<IThermalChangedCallback> &callback,
TemperatureType type) override;
ndk::ScopedAStatus unregisterThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback> &callback) override;
binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
// Helper function for calling callbacks
void sendThermalChangedCallback(const Temperature &t);
private:
class Looper {
public:
struct Event {
std::function<void()> handler;
};
Looper() {
thread_ = std::thread([&] { loop(); });
}
void addEvent(const Event &e);
private:
std::condition_variable cv_;
std::queue<Event> events_;
std::mutex mutex_;
std::thread thread_;
void loop();
};
ThermalHelper thermal_helper_;
std::mutex thermal_callback_mutex_;
std::vector<CallbackSetting> callbacks_;
Looper looper_;
ndk::ScopedAStatus getFilteredTemperatures(bool filterType, TemperatureType type,
std::vector<Temperature> *_aidl_return);
ndk::ScopedAStatus getFilteredCoolingDevices(bool filterType, CoolingType type,
std::vector<CoolingDevice> *_aidl_return);
ndk::ScopedAStatus getFilteredTemperatureThresholds(
bool filterType, TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return);
ndk::ScopedAStatus registerThermalChangedCallback(
const std::shared_ptr<IThermalChangedCallback> &callback, bool filterType,
TemperatureType type);
void dumpVirtualSensorInfo(std::ostringstream *dump_buf);
void dumpThrottlingInfo(std::ostringstream *dump_buf);
void dumpThrottlingRequestStatus(std::ostringstream *dump_buf);
void dumpPowerRailInfo(std::ostringstream *dump_buf);
void dumpStatsRecord(std::ostringstream *dump_buf, const StatsRecord &stats_record,
std::string_view line_prefix);
void dumpThermalStats(std::ostringstream *dump_buf);
void dumpThermalData(int fd);
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,14 @@
on property:vendor.thermal.link_ready=1
# queue the trigger to start thermal-hal and continue execute
# per-device thermal setup "on property:vendor.thermal.link_ready=1"
trigger enable-thermal-hal
on enable-thermal-hal
restart vendor.thermal-hal
service vendor.thermal-hal /vendor/bin/hw/android.hardware.thermal-service.pixel
class hal
user system
group system
priority -20
disabled

@ -0,0 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.thermal</name>
<version>1</version>
<fqname>IThermal/default</fqname>
</hal>
</manifest>

@ -0,0 +1,25 @@
#!/vendor/bin/sh
if [ $# -eq 1 ]; then
interval=$1
else
exit 1
fi
while true
do
logline="tz:"
for f in /sys/class/thermal/thermal*
do
temp=`cat $f/temp`
logline+="|$temp"
done
logline+=" cdev:"
for f in /sys/class/thermal/cooling_device*
do
cur_state=`cat $f/cur_state`
logline+="|$cur_state"
done
log -p w -t THERMAL_LOG $logline
sleep $interval
done

@ -0,0 +1,13 @@
#!/vendor/bin/sh
for f in /sys/class/thermal/thermal_zone*
do
tz_name=`cat $f/type`
ln -s $f /dev/thermal/tz-by-name/$tz_name
done
for f in /sys/class/thermal/cooling_device*
do
cdev_name=`cat $f/type`
ln -s $f /dev/thermal/cdev-by-name/$cdev_name
done
setprop vendor.thermal.link_ready 1

@ -0,0 +1,130 @@
on property:persist.vendor.log.thermal=1
start vendor.thermal.logd
on property:persist.vendor.log.thermal=0
stop vendor.thermal.logd
on property:persist.vendor.log.thermal=1 && property:persist.vendor.log.thermal.interval=*
restart vendor.thermal.logd
service vendor.thermal.logd /vendor/bin/thermal_logd ${persist.vendor.log.thermal.interval:-5}
class main
user root
group root system
disabled
# Switch thermal protection for Pixels
on property:persist.vendor.disable.thermal.control=*
setprop vendor.disable.thermal.control ${persist.vendor.disable.thermal.control}
on property:persist.vendor.disable.thermalhal.control=*
setprop vendor.disable.thermalhal.control ${persist.vendor.disable.thermalhal.control}
on property:persist.vendor.disable.usb.overheat.mitigation=*
setprop vendor.disable.usb.overheat.mitigation.control ${persist.vendor.disable.usb.overheat.mitigation}
on property:persist.vendor.disable.bcl.control=*
setprop vendor.disable.bcl.control ${persist.vendor.disable.bcl.control}
on property:vendor.disable.thermalhal.control=* && property:vendor.thermal.link_ready=1
restart vendor.thermal-hal
on property:vendor.disable.thermal.control=1 && property:vendor.thermal.link_ready=1
# common
stop vendor.thermal-engine
setprop vendor.disable.thermalhal.control 1
# sdm845
write /dev/thermal/tz-by-name/quiet-therm-adc/mode disabled
write /dev/thermal/tz-by-name/quiet-therm-monitor/mode disabled
write /dev/thermal/tz-by-name/fps-therm-adc/mode disabled
write /dev/thermal/tz-by-name/fps-therm-monitor/mode disabled
# sdm670
write /dev/thermal/tz-by-name/mb-therm-adc/mode disabled
write /dev/thermal/tz-by-name/mb-therm-monitor/mode disabled
# sm8150
write /dev/thermal/tz-by-name/sdm-therm/mode disabled
write /dev/thermal/tz-by-name/sdm-therm-monitor/mode disabled
# sm7150
write /dev/thermal/tz-by-name/skin-therm-adc/mode disabled
write /dev/thermal/tz-by-name/skin-therm-monitor/mode disabled
# sm7250
write /dev/thermal/tz-by-name/skin-therm/emul_temp 25000
write /dev/thermal/tz-by-name/skin-therm/mode disabled
write /dev/thermal/tz-by-name/skin-virt/emul_temp 25000
write /dev/thermal/tz-by-name/skin-virt/mode disabled
write /dev/thermal/tz-by-name/skin-therm-cpu/emul_temp 25000
write /dev/thermal/tz-by-name/skin-therm-cpu/mode disabled
write /dev/thermal/tz-by-name/skin-virt-cpu/emul_temp 25000
write /dev/thermal/tz-by-name/skin-virt-cpu/mode disabled
write /dev/thermal/tz-by-name/skin-therm-monitor/emul_temp 25000
write /dev/thermal/tz-by-name/skin-therm-monitor/mode disabled
write /dev/thermal/tz-by-name/skin-virt-monitor/emul_temp 25000
write /dev/thermal/tz-by-name/skin-virt-monitor/mode disabled
write /dev/thermal/tz-by-name/panel-audio-therm/emul_temp 25000
write /dev/thermal/tz-by-name/panel-audio-therm/mode disabled
write /dev/thermal/tz-by-name/cellular-emergency/emul_temp 25000
write /dev/thermal/tz-by-name/cellular-emergency/mode disabled
write /dev/thermal/tz-by-name/sdm-therm/emul_temp 25000
write /dev/thermal/tz-by-name/sdm-therm/mode disabled
write /dev/thermal/tz-by-name/charger-therm/emul_temp 25000
write /dev/thermal/tz-by-name/charger-therm/mode disabled
# P21
write /dev/thermal/tz-by-name/disp_therm/mode disabled
on property:vendor.disable.thermal.control=0 && property:vendor.thermal.link_ready=1
# common
start vendor.thermal-engine
setprop vendor.disable.thermalhal.control 0
# sdm845
write /dev/thermal/tz-by-name/quiet-therm-adc/mode enabled
write /dev/thermal/tz-by-name/quiet-therm-monitor/mode enabled
write /dev/thermal/tz-by-name/fps-therm-adc/mode enabled
write /dev/thermal/tz-by-name/fps-therm-monitor/mode enabled
# sdm670
write /dev/thermal/tz-by-name/mb-therm-adc/mode enabled
write /dev/thermal/tz-by-name/mb-therm-monitor/mode enabled
# sm8150
write /dev/thermal/tz-by-name/sdm-therm/mode enabled
write /dev/thermal/tz-by-name/sdm-therm-monitor/mode enabled
# sm7150
write /dev/thermal/tz-by-name/skin-therm-adc/mode enabled
write /dev/thermal/tz-by-name/skin-therm-monitor/mode enabled
# sm7250
write /dev/thermal/tz-by-name/skin-therm/emul_temp 0
write /dev/thermal/tz-by-name/skin-therm/mode enabled
write /dev/thermal/tz-by-name/skin-virt/emul_temp 0
write /dev/thermal/tz-by-name/skin-virt/mode enabled
write /dev/thermal/tz-by-name/skin-therm-cpu/emul_temp 0
write /dev/thermal/tz-by-name/skin-therm-cpu/mode enabled
write /dev/thermal/tz-by-name/skin-virt-cpu/emul_temp 0
write /dev/thermal/tz-by-name/skin-virt-cpu/mode enabled
write /dev/thermal/tz-by-name/skin-therm-monitor/emul_temp 0
write /dev/thermal/tz-by-name/skin-therm-monitor/mode enabled
write /dev/thermal/tz-by-name/skin-virt-monitor/emul_temp 0
write /dev/thermal/tz-by-name/skin-virt-monitor/mode enabled
write /dev/thermal/tz-by-name/panel-audio-therm/emul_temp 0
write /dev/thermal/tz-by-name/panel-audio-therm/mode enabled
write /dev/thermal/tz-by-name/cellular-emergency/emul_temp 0
write /dev/thermal/tz-by-name/cellular-emergency/mode enabled
write /dev/thermal/tz-by-name/sdm-therm/emul_temp 0
write /dev/thermal/tz-by-name/sdm-therm/mode enabled
write /dev/thermal/tz-by-name/charger-therm/emul_temp 0
write /dev/thermal/tz-by-name/charger-therm/mode enabled
# P21
write /dev/thermal/tz-by-name/disp_therm/mode enabled
# Toggle BCL control
on property:vendor.disable.bcl.control=1
write /dev/thermal/tz-by-name/soc/mode disabled
on property:vendor.disable.bcl.control=0
write /dev/thermal/tz-by-name/soc/mode enabled
# Switch USB port overheat protection
on property:vendor.disable.usb.overheat.mitigation.control=1
write /sys/module/overheat_mitigation/parameters/enable 0
write /dev/thermal/tz-by-name/usb_pwr_therm2/emul_temp 25000
on property:vendor.disable.usb.overheat.mitigation.control=0
write /sys/module/overheat_mitigation/parameters/enable 1
write /dev/thermal/tz-by-name/usb_pwr_therm2/emul_temp 0

@ -0,0 +1,11 @@
on boot
mkdir /dev/thermal 0750 system system
mkdir /dev/thermal/tz-by-name 0750 system system
mkdir /dev/thermal/cdev-by-name 0750 system system
start vendor.thermal.symlinks
service vendor.thermal.symlinks /vendor/bin/thermal_symlinks
user system
group system
oneshot
disabled

@ -0,0 +1,51 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include "Thermal.h"
constexpr std::string_view kThermalLogTag("pixel-thermal");
using ::android::OK;
using ::android::status_t;
// Generated AIDL files:
using Thermal = ::aidl::android::hardware::thermal::implementation::Thermal;
#if !defined(THERMAL_INSTANCE_NAME)
#define THERMAL_INSTANCE_NAME "default"
#endif
int main(int /* argc */, char ** /* argv */) {
android::base::SetDefaultTag(kThermalLogTag.data());
auto svc = ndk::SharedRefBase::make<Thermal>();
const auto svcName = std::string() + svc->descriptor + "/" + THERMAL_INSTANCE_NAME;
LOG(INFO) << "Pixel Thermal AIDL Service starting..." + svcName;
ABinderProcess_setThreadPoolMaxThreadCount(0);
auto svcBinder = svc->asBinder();
binder_status_t status = AServiceManager_addService(svcBinder.get(), svcName.c_str());
if (status != STATUS_OK) {
LOG(ERROR) << "Pixel Thermal AIDL Service failed to start: " << status << ".";
return EXIT_FAILURE;
}
LOG(INFO) << "Pixel Thermal HAL AIDL Service started.";
ABinderProcess_joinThreadPool();
return EXIT_FAILURE; // should not reach
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,196 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/hardware/thermal/IThermal.h>
#include <array>
#include <chrono>
#include <map>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <vector>
#include "utils/power_files.h"
#include "utils/powerhal_helper.h"
#include "utils/thermal_files.h"
#include "utils/thermal_info.h"
#include "utils/thermal_stats_helper.h"
#include "utils/thermal_throttling.h"
#include "utils/thermal_watcher.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::android::sp;
using NotificationCallback = std::function<void(const Temperature &t)>;
// Get thermal_zone type
bool getThermalZoneTypeById(int tz_id, std::string *);
struct ThermalSample {
float temp;
boot_clock::time_point timestamp;
};
struct EmulSetting {
float emul_temp;
int emul_severity;
bool pending_update;
};
struct SensorStatus {
ThrottlingSeverity severity;
ThrottlingSeverity prev_hot_severity;
ThrottlingSeverity prev_cold_severity;
ThrottlingSeverity prev_hint_severity;
boot_clock::time_point last_update_time;
ThermalSample thermal_cached;
std::unique_ptr<EmulSetting> emul_setting;
};
class ThermalHelper {
public:
explicit ThermalHelper(const NotificationCallback &cb);
~ThermalHelper() = default;
bool fillCurrentTemperatures(bool filterType, bool filterCallback, TemperatureType type,
std::vector<Temperature> *temperatures);
bool fillTemperatureThresholds(bool filterType, TemperatureType type,
std::vector<TemperatureThreshold> *thresholds) const;
bool fillCurrentCoolingDevices(bool filterType, CoolingType type,
std::vector<CoolingDevice> *coolingdevices) const;
bool emulTemp(std::string_view target_sensor, const float temp);
bool emulSeverity(std::string_view target_sensor, const int severity);
bool emulClear(std::string_view target_sensor);
// Disallow copy and assign.
ThermalHelper(const ThermalHelper &) = delete;
void operator=(const ThermalHelper &) = delete;
bool isInitializedOk() const { return is_initialized_; }
// Read the temperature of a single sensor.
bool readTemperature(std::string_view sensor_name, Temperature *out);
bool readTemperature(
std::string_view sensor_name, Temperature *out,
std::pair<ThrottlingSeverity, ThrottlingSeverity> *throtting_status = nullptr,
const bool force_sysfs = false);
bool readTemperatureThreshold(std::string_view sensor_name, TemperatureThreshold *out) const;
// Read the value of a single cooling device.
bool readCoolingDevice(std::string_view cooling_device, CoolingDevice *out) const;
// Get SensorInfo Map
const std::unordered_map<std::string, SensorInfo> &GetSensorInfoMap() const {
return sensor_info_map_;
}
// Get CdevInfo Map
const std::unordered_map<std::string, CdevInfo> &GetCdevInfoMap() const {
return cooling_device_info_map_;
}
// Get SensorStatus Map
const std::unordered_map<std::string, SensorStatus> &GetSensorStatusMap() const {
std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
return sensor_status_map_;
}
// Get ThermalThrottling Map
const std::unordered_map<std::string, ThermalThrottlingStatus> &GetThermalThrottlingStatusMap()
const {
return thermal_throttling_.GetThermalThrottlingStatusMap();
}
// Get PowerRailInfo Map
const std::unordered_map<std::string, PowerRailInfo> &GetPowerRailInfoMap() const {
return power_files_.GetPowerRailInfoMap();
}
// Get PowerStatus Map
const std::unordered_map<std::string, PowerStatus> &GetPowerStatusMap() const {
return power_files_.GetPowerStatusMap();
}
// Get Thermal Stats Sensor Map
const std::unordered_map<std::string, SensorTempStats> GetSensorTempStatsSnapshot() {
return thermal_stats_helper_.GetSensorTempStatsSnapshot();
}
// Get Thermal Stats Sensor, Binded Cdev State Request Map
const std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
GetSensorCoolingDeviceRequestStatsSnapshot() {
return thermal_stats_helper_.GetSensorCoolingDeviceRequestStatsSnapshot();
}
void sendPowerExtHint(const Temperature &t);
bool isAidlPowerHalExist() { return power_hal_service_.isAidlPowerHalExist(); }
bool isPowerHalConnected() { return power_hal_service_.isPowerHalConnected(); }
bool isPowerHalExtConnected() { return power_hal_service_.isPowerHalExtConnected(); }
private:
bool initializeSensorMap(const std::unordered_map<std::string, std::string> &path_map);
bool initializeCoolingDevices(const std::unordered_map<std::string, std::string> &path_map);
bool isSubSensorValid(std::string_view sensor_data, const SensorFusionType sensor_fusion_type);
void setMinTimeout(SensorInfo *sensor_info);
void initializeTrip(const std::unordered_map<std::string, std::string> &path_map,
std::set<std::string> *monitored_sensors, bool thermal_genl_enabled);
void clearAllThrottling();
// For thermal_watcher_'s polling thread, return the sleep interval
std::chrono::milliseconds thermalWatcherCallbackFunc(
const std::set<std::string> &uevent_sensors);
// Return hot and cold severity status as std::pair
std::pair<ThrottlingSeverity, ThrottlingSeverity> getSeverityFromThresholds(
const ThrottlingArray &hot_thresholds, const ThrottlingArray &cold_thresholds,
const ThrottlingArray &hot_hysteresis, const ThrottlingArray &cold_hysteresis,
ThrottlingSeverity prev_hot_severity, ThrottlingSeverity prev_cold_severity,
float value) const;
// Read sensor data according to the type
bool readDataByType(std::string_view sensor_data, float *reading_value,
const SensorFusionType type, const bool force_no_cache,
std::map<std::string, float> *sensor_log_map);
// Read temperature data according to thermal sensor's info
bool readThermalSensor(std::string_view sensor_name, float *temp, const bool force_sysfs,
std::map<std::string, float> *sensor_log_map);
bool connectToPowerHal();
void updateSupportedPowerHints();
void updateCoolingDevices(const std::vector<std::string> &cooling_devices_to_update);
sp<ThermalWatcher> thermal_watcher_;
PowerFiles power_files_;
ThermalFiles thermal_sensors_;
ThermalFiles cooling_devices_;
ThermalThrottling thermal_throttling_;
bool is_initialized_;
const NotificationCallback cb_;
std::unordered_map<std::string, CdevInfo> cooling_device_info_map_;
std::unordered_map<std::string, SensorInfo> sensor_info_map_;
std::unordered_map<std::string, std::unordered_map<ThrottlingSeverity, ThrottlingSeverity>>
supported_powerhint_map_;
PowerHalService power_hal_service_;
ThermalStatsHelper thermal_stats_helper_;
mutable std::shared_mutex sensor_status_map_mutex_;
std::unordered_map<std::string, SensorStatus> sensor_status_map_;
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,219 @@
{
"definitions":{
},
"$schema":"http://json-schema.org/draft-07/schema#",
"$id":"http://example.com/root.json",
"type":"object",
"title":"The Root Schema",
"required":[
"Sensors"
],
"properties":{
"Sensors":{
"$id":"#/properties/Sensors",
"type":"array",
"title":"The Sensors Schema",
"items":{
"$id":"#/properties/Sensors/items",
"type":"object",
"title":"The Items Schema",
"required":[
"Name",
"Type",
"HotThreshold",
"VrThreshold",
"Multiplier"
],
"properties":{
"Name":{
"$id":"#/properties/Sensors/items/properties/Name",
"type":"string",
"title":"The Name Schema",
"default":"",
"examples":[
"cpu0-silver-usr"
],
"pattern":"^(.+)$"
},
"Type":{
"$id":"#/properties/Sensors/items/properties/Type",
"type":"string",
"title":"The Type Schema",
"default":"",
"examples":[
"CPU"
],
"pattern":"^(.+)$"
},
"HotThreshold":{
"$id":"#/properties/Sensors/items/properties/HotThreshold",
"type":"array",
"title":"The hot threshold Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN",
"default":"NAN",
"maxItems":7,
"minItems":7,
"items":{
"$id":"#/properties/Sensors/items/properties/HotThreshold/items",
"type":[
"string",
"number"
],
"title":"The Items Schema",
"default":"",
"examples":[
"NAN",
"NAN",
"NAN",
95,
"NAN",
"NAN",
125
],
"pattern":"^([-+]?[0-9]*\\.?[0-9]+|NAN)$"
}
},
"HotHysteresis":{
"$id":"#/properties/Sensors/items/properties/HotHysteresis",
"type":"array",
"title":"The hot hysteresis Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN. Throttling status will be cleared HotThreshold - HotHysteresis.",
"default":null,
"maxItems":7,
"minItems":7,
"items":{
"$id":"#/properties/Sensors/items/properties/HotHysteresis/items",
"type":[
"number"
],
"title":"The Items Schema",
"default":0.0,
"examples":[
0.0,
0.0,
0.0,
1.0,
1.5,
1.0,
2.0
]
}
},
"ColdThreshold":{
"$id":"#/properties/Sensors/items/properties/ColdThreshold",
"type":"array",
"title":"The cold threshold Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN, default to NAN",
"default":null,
"maxItems":7,
"minItems":7,
"items":{
"$id":"#/properties/Sensors/items/properties/ColdThreshold/items",
"type":"string",
"title":"The Items Schema",
"default":"NAN",
"examples":[
"NAN",
"NAN",
"NAN",
"NAN",
"NAN",
"NAN",
"NAN"
],
"pattern":"^([-+]?[0-9]*\\.?[0-9]+|NAN)$"
}
},
"ColdHysteresis":{
"$id":"#/properties/Sensors/items/properties/ColdHysteresis",
"type":"array",
"title":"The cold hysteresis Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN. Throttling status will be cleared ColdThreshold + ColdHysteresis.",
"default":null,
"maxItems":7,
"minItems":7,
"items":{
"$id":"#/properties/Sensors/items/properties/ColdHysteresis/items",
"type":[
"number"
],
"title":"The Items Schema",
"default":0.0,
"examples":[
0.0,
0.0,
0.0,
1.0,
1.5,
1.0,
2.0
]
}
},
"VrThreshold":{
"$id":"#/properties/Sensors/items/properties/VrThreshold",
"type":"string",
"title":"The Vrthreshold Schema",
"default":"",
"examples":[
"NAN"
],
"pattern":"^(.*)$"
},
"Multiplier":{
"$id":"#/properties/Sensors/items/properties/Multiplier",
"type":"number",
"title":"The Multiplier Schema",
"default":0.001,
"examples":[
0.001
],
"exclusiveMinimum":0.0
},
"Monitor":{
"$id":"#/properties/Sensors/items/properties/Monitor",
"type":"boolean",
"title":"The Monitor Schema, if the sensor will be monitored and used to trigger throttling event",
"default":false,
"examples":[
true
]
}
}
}
},
"CoolingDevices":{
"$id":"#/properties/CoolingDevices",
"type":"array",
"title":"The Coolingdevices Schema",
"items":{
"$id":"#/properties/CoolingDevices/items",
"type":"object",
"title":"The Items Schema",
"required":[
"Name",
"Type"
],
"properties":{
"Name":{
"$id":"#/properties/CoolingDevices/items/properties/Name",
"type":"string",
"title":"The Name Schema",
"default":"",
"examples":[
"thermal-cpufreq-0"
],
"pattern":"^(.+)$"
},
"Type":{
"$id":"#/properties/CoolingDevices/items/properties/Type",
"type":"string",
"title":"The Type Schema",
"default":"",
"examples":[
"CPU"
],
"pattern":"^(.+)$"
}
}
}
}
}
}

@ -0,0 +1,342 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
#include "power_files.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <dirent.h>
#include <utils/Trace.h>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
constexpr std::string_view kDeviceType("iio:device");
constexpr std::string_view kIioRootDir("/sys/bus/iio/devices");
constexpr std::string_view kEnergyValueNode("energy_value");
using ::android::base::ReadFileToString;
using ::android::base::StringPrintf;
bool PowerFiles::registerPowerRailsToWatch(const Json::Value &config) {
if (!ParsePowerRailInfo(config, &power_rail_info_map_)) {
LOG(ERROR) << "Failed to parse power rail info config";
return false;
}
if (!power_rail_info_map_.size()) {
LOG(INFO) << " No power rail info config found";
return true;
}
if (!findEnergySourceToWatch()) {
LOG(ERROR) << "Cannot find energy source";
return false;
}
if (!energy_info_map_.size() && !updateEnergyValues()) {
LOG(ERROR) << "Faield to update energy info";
return false;
}
for (const auto &power_rail_info_pair : power_rail_info_map_) {
std::vector<std::queue<PowerSample>> power_history;
if (!power_rail_info_pair.second.power_sample_count ||
power_rail_info_pair.second.power_sample_delay == std::chrono::milliseconds::max()) {
continue;
}
PowerSample power_sample = {
.energy_counter = 0,
.duration = 0,
};
if (power_rail_info_pair.second.virtual_power_rail_info != nullptr &&
power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size()) {
for (size_t i = 0;
i < power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size();
++i) {
if (!energy_info_map_.count(power_rail_info_pair.second.virtual_power_rail_info
->linked_power_rails[i])) {
LOG(ERROR) << " Could not find energy source "
<< power_rail_info_pair.second.virtual_power_rail_info
->linked_power_rails[i];
return false;
}
power_history.emplace_back(std::queue<PowerSample>());
for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
power_history[i].emplace(power_sample);
}
}
} else {
if (energy_info_map_.count(power_rail_info_pair.first)) {
power_history.emplace_back(std::queue<PowerSample>());
for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
power_history[0].emplace(power_sample);
}
} else {
LOG(ERROR) << "Could not find energy source " << power_rail_info_pair.first;
return false;
}
}
if (power_history.size()) {
power_status_map_[power_rail_info_pair.first] = {
.power_history = power_history,
.last_update_time = boot_clock::time_point::min(),
.last_updated_avg_power = NAN,
};
} else {
LOG(ERROR) << "power history size is zero";
return false;
}
LOG(INFO) << "Successfully to register power rail " << power_rail_info_pair.first;
}
return true;
}
bool PowerFiles::findEnergySourceToWatch(void) {
std::string devicePath;
if (energy_path_set_.size()) {
return true;
}
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kIioRootDir.data()), closedir);
if (!dir) {
PLOG(ERROR) << "Error opening directory" << kIioRootDir;
return false;
}
// Find any iio:devices that support energy_value
while (struct dirent *ent = readdir(dir.get())) {
std::string devTypeDir = ent->d_name;
if (devTypeDir.find(kDeviceType) != std::string::npos) {
devicePath = StringPrintf("%s/%s", kIioRootDir.data(), devTypeDir.data());
std::string deviceEnergyContent;
if (!ReadFileToString(StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()),
&deviceEnergyContent)) {
} else if (deviceEnergyContent.size()) {
energy_path_set_.emplace(
StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()));
}
}
}
if (!energy_path_set_.size()) {
return false;
}
return true;
}
bool PowerFiles::updateEnergyValues(void) {
std::string deviceEnergyContent;
std::string deviceEnergyContents;
std::string line;
ATRACE_CALL();
for (const auto &path : energy_path_set_) {
if (!::android::base::ReadFileToString(path, &deviceEnergyContent)) {
LOG(ERROR) << "Failed to read energy content from " << path;
return false;
} else {
deviceEnergyContents.append(deviceEnergyContent);
}
}
std::istringstream energyData(deviceEnergyContents);
while (std::getline(energyData, line)) {
/* Read rail energy */
uint64_t energy_counter = 0;
uint64_t duration = 0;
/* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */
auto start_pos = line.find("T=");
auto end_pos = line.find(')');
if (start_pos != std::string::npos) {
duration =
strtoul(line.substr(start_pos + 2, end_pos - start_pos - 2).c_str(), NULL, 10);
} else {
continue;
}
start_pos = line.find(")[");
end_pos = line.find(']');
std::string railName;
if (start_pos != std::string::npos) {
railName = line.substr(start_pos + 2, end_pos - start_pos - 2);
} else {
continue;
}
start_pos = line.find("],");
if (start_pos != std::string::npos) {
energy_counter = strtoul(line.substr(start_pos + 2).c_str(), NULL, 10);
} else {
continue;
}
energy_info_map_[railName] = {
.energy_counter = energy_counter,
.duration = duration,
};
}
return true;
}
float PowerFiles::updateAveragePower(std::string_view power_rail,
std::queue<PowerSample> *power_history) {
float avg_power = NAN;
if (!energy_info_map_.count(power_rail.data())) {
LOG(ERROR) << " Could not find power rail " << power_rail.data();
return avg_power;
}
const auto last_sample = power_history->front();
const auto curr_sample = energy_info_map_.at(power_rail.data());
const auto duration = curr_sample.duration - last_sample.duration;
const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter;
if (!last_sample.duration) {
LOG(VERBOSE) << "Power rail " << power_rail.data()
<< ": all power samples have not been collected yet";
} else if (duration <= 0 || deltaEnergy < 0) {
LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: duration = " << duration
<< ", deltaEnergy = " << deltaEnergy;
return avg_power;
} else {
avg_power = static_cast<float>(deltaEnergy) / static_cast<float>(duration);
LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << avg_power
<< ", duration = " << duration << ", deltaEnergy = " << deltaEnergy;
}
power_history->pop();
power_history->push(curr_sample);
return avg_power;
}
float PowerFiles::updatePowerRail(std::string_view power_rail) {
float avg_power = NAN;
if (!power_rail_info_map_.count(power_rail.data())) {
return avg_power;
}
if (!power_status_map_.count(power_rail.data())) {
return avg_power;
}
const auto &power_rail_info = power_rail_info_map_.at(power_rail.data());
auto &power_status = power_status_map_.at(power_rail.data());
boot_clock::time_point now = boot_clock::now();
auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now - power_status.last_update_time);
if (power_status.last_update_time != boot_clock::time_point::min() &&
time_elapsed_ms < power_rail_info.power_sample_delay) {
return power_status.last_updated_avg_power;
}
if (!energy_info_map_.size() && !updateEnergyValues()) {
LOG(ERROR) << "Failed to update energy values";
return avg_power;
}
if (power_rail_info.virtual_power_rail_info == nullptr) {
avg_power = updateAveragePower(power_rail, &power_status.power_history[0]);
} else {
const auto offset = power_rail_info.virtual_power_rail_info->offset;
float avg_power_val = 0.0;
for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
i++) {
float coefficient = power_rail_info.virtual_power_rail_info->coefficients[i];
float avg_power_number = updateAveragePower(
power_rail_info.virtual_power_rail_info->linked_power_rails[i],
&power_status.power_history[i]);
switch (power_rail_info.virtual_power_rail_info->formula) {
case FormulaOption::COUNT_THRESHOLD:
if ((coefficient < 0 && avg_power_number < -coefficient) ||
(coefficient >= 0 && avg_power_number >= coefficient))
avg_power_val += 1;
break;
case FormulaOption::WEIGHTED_AVG:
avg_power_val += avg_power_number * coefficient;
break;
case FormulaOption::MAXIMUM:
if (i == 0)
avg_power_val = std::numeric_limits<float>::lowest();
if (avg_power_number * coefficient > avg_power_val)
avg_power_val = avg_power_number * coefficient;
break;
case FormulaOption::MINIMUM:
if (i == 0)
avg_power_val = std::numeric_limits<float>::max();
if (avg_power_number * coefficient < avg_power_val)
avg_power_val = avg_power_number * coefficient;
break;
default:
break;
}
}
if (avg_power_val >= 0) {
avg_power_val = avg_power_val + offset;
}
avg_power = avg_power_val;
}
if (avg_power < 0) {
avg_power = NAN;
}
power_status.last_updated_avg_power = avg_power;
power_status.last_update_time = now;
return avg_power;
}
bool PowerFiles::refreshPowerStatus(void) {
if (!updateEnergyValues()) {
LOG(ERROR) << "Failed to update energy values";
return false;
}
for (const auto &power_status_pair : power_status_map_) {
updatePowerRail(power_status_pair.first);
}
return true;
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,95 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android-base/chrono_utils.h>
#include <chrono>
#include <queue>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "thermal_info.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::android::base::boot_clock;
struct PowerSample {
uint64_t energy_counter;
uint64_t duration;
};
struct PowerStatus {
boot_clock::time_point last_update_time;
// A vector to record the queues of power sample history.
std::vector<std::queue<PowerSample>> power_history;
float last_updated_avg_power;
};
// A helper class for monitoring power rails.
class PowerFiles {
public:
PowerFiles() = default;
~PowerFiles() = default;
// Disallow copy and assign.
PowerFiles(const PowerFiles &) = delete;
void operator=(const PowerFiles &) = delete;
bool registerPowerRailsToWatch(const Json::Value &config);
// Update the power data from ODPM sysfs
bool refreshPowerStatus(void);
// Get power status map
const std::unordered_map<std::string, PowerStatus> &GetPowerStatusMap() const {
std::shared_lock<std::shared_mutex> _lock(power_status_map_mutex_);
return power_status_map_;
}
// Get power rail info map
const std::unordered_map<std::string, PowerRailInfo> &GetPowerRailInfoMap() const {
return power_rail_info_map_;
}
private:
// Update energy value to energy_info_map_, return false if the value is failed to update.
bool updateEnergyValues(void);
// Compute the average power for physical power rail.
float updateAveragePower(std::string_view power_rail, std::queue<PowerSample> *power_history);
// Update the power data for the target power rail.
float updatePowerRail(std::string_view power_rail);
// Find the energy source path, return false if no energy source found.
bool findEnergySourceToWatch(void);
// The map to record the energy counter for each power rail.
std::unordered_map<std::string, PowerSample> energy_info_map_;
// The map to record the power data for each thermal sensor.
std::unordered_map<std::string, PowerStatus> power_status_map_;
mutable std::shared_mutex power_status_map_mutex_;
// The map to record the power rail information from thermal config
std::unordered_map<std::string, PowerRailInfo> power_rail_info_map_;
// The set to store the energy source paths
std::unordered_set<std::string> energy_path_set_;
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,138 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "powerhal_helper.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android/binder_manager.h>
#include <iterator>
#include <set>
#include <sstream>
#include <thread>
#include <vector>
#include "thermal_info.h"
#include "thermal_throttling.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::android::base::StringPrintf;
PowerHalService::PowerHalService()
: power_hal_aidl_exist_(true), power_hal_aidl_(nullptr), power_hal_ext_aidl_(nullptr) {
connect();
}
bool PowerHalService::connect() {
std::lock_guard<std::mutex> lock(lock_);
if (!power_hal_aidl_exist_) {
return false;
}
if (power_hal_aidl_ && power_hal_ext_aidl_) {
return true;
}
const std::string kInstance = std::string(IPower::descriptor) + "/default";
ndk::SpAIBinder power_binder = ndk::SpAIBinder(AServiceManager_getService(kInstance.c_str()));
ndk::SpAIBinder ext_power_binder;
if (power_binder.get() == nullptr) {
LOG(ERROR) << "Cannot get Power Hal Binder";
power_hal_aidl_exist_ = false;
return false;
}
power_hal_aidl_ = IPower::fromBinder(power_binder);
if (power_hal_aidl_ == nullptr) {
power_hal_aidl_exist_ = false;
LOG(ERROR) << "Cannot get Power Hal AIDL" << kInstance.c_str();
return false;
}
if (STATUS_OK != AIBinder_getExtension(power_binder.get(), ext_power_binder.getR()) ||
ext_power_binder.get() == nullptr) {
LOG(ERROR) << "Cannot get Power Hal Extension Binder";
power_hal_aidl_exist_ = false;
return false;
}
power_hal_ext_aidl_ = IPowerExt::fromBinder(ext_power_binder);
if (power_hal_ext_aidl_ == nullptr) {
LOG(ERROR) << "Cannot get Power Hal Extension AIDL";
power_hal_aidl_exist_ = false;
}
return true;
}
bool PowerHalService::isModeSupported(const std::string &type, const ThrottlingSeverity &t) {
bool isSupported = false;
if (!connect()) {
return false;
}
std::string power_hint = StringPrintf("THERMAL_%s_%s", type.c_str(), toString(t).c_str());
lock_.lock();
if (!power_hal_ext_aidl_->isModeSupported(power_hint, &isSupported).isOk()) {
LOG(ERROR) << "Fail to check supported mode, Hint: " << power_hint;
power_hal_ext_aidl_ = nullptr;
power_hal_aidl_ = nullptr;
lock_.unlock();
return false;
}
lock_.unlock();
return isSupported;
}
void PowerHalService::setMode(const std::string &type, const ThrottlingSeverity &t,
const bool &enable, const bool error_on_exit) {
if (!connect()) {
return;
}
std::string power_hint = StringPrintf("THERMAL_%s_%s", type.c_str(), toString(t).c_str());
LOG(INFO) << (error_on_exit ? "Resend Hint " : "Send Hint ") << power_hint
<< " Enable: " << std::boolalpha << enable;
lock_.lock();
if (!power_hal_ext_aidl_->setMode(power_hint, enable).isOk()) {
LOG(ERROR) << "Fail to set mode, Hint: " << power_hint;
power_hal_ext_aidl_ = nullptr;
power_hal_aidl_ = nullptr;
lock_.unlock();
if (!error_on_exit) {
setMode(type, t, enable, true);
}
return;
}
lock_.unlock();
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/hardware/power/IPower.h>
#include <aidl/android/hardware/thermal/ThrottlingSeverity.h>
#include <aidl/google/hardware/power/extension/pixel/IPowerExt.h>
#include <queue>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::aidl::android::hardware::power::IPower;
using ::aidl::google::hardware::power::extension::pixel::IPowerExt;
using CdevRequestStatus = std::unordered_map<std::string, int>;
class PowerHalService {
public:
PowerHalService();
~PowerHalService() = default;
bool connect();
bool isAidlPowerHalExist() { return power_hal_aidl_exist_; }
bool isModeSupported(const std::string &type, const ThrottlingSeverity &t);
bool isPowerHalConnected() { return power_hal_aidl_ != nullptr; }
bool isPowerHalExtConnected() { return power_hal_ext_aidl_ != nullptr; }
void setMode(const std::string &type, const ThrottlingSeverity &t, const bool &enable,
const bool error_on_exit = false);
private:
bool power_hal_aidl_exist_;
std::shared_ptr<IPower> power_hal_aidl_;
std::shared_ptr<IPowerExt> power_hal_ext_aidl_;
std::mutex lock_;
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,88 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
#include "thermal_files.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <utils/Trace.h>
#include <algorithm>
#include <string_view>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::android::base::StringPrintf;
std::string ThermalFiles::getThermalFilePath(std::string_view thermal_name) const {
auto sensor_itr = thermal_name_to_path_map_.find(thermal_name.data());
if (sensor_itr == thermal_name_to_path_map_.end()) {
return "";
}
return sensor_itr->second;
}
bool ThermalFiles::addThermalFile(std::string_view thermal_name, std::string_view path) {
return thermal_name_to_path_map_.emplace(thermal_name, path).second;
}
bool ThermalFiles::readThermalFile(std::string_view thermal_name, std::string *data) const {
std::string sensor_reading;
std::string file_path = getThermalFilePath(std::string_view(thermal_name));
*data = "";
ATRACE_NAME(StringPrintf("ThermalFiles::readThermalFile - %s", thermal_name.data()).c_str());
if (file_path.empty()) {
PLOG(WARNING) << "Failed to find " << thermal_name << "'s path";
return false;
}
if (!::android::base::ReadFileToString(file_path, &sensor_reading)) {
PLOG(WARNING) << "Failed to read sensor: " << thermal_name;
return false;
}
// Strip the newline.
*data = ::android::base::Trim(sensor_reading);
return true;
}
bool ThermalFiles::writeCdevFile(std::string_view cdev_name, std::string_view data) {
std::string file_path =
getThermalFilePath(::android::base::StringPrintf("%s_%s", cdev_name.data(), "w"));
ATRACE_NAME(StringPrintf("ThermalFiles::writeCdevFile - %s", cdev_name.data()).c_str());
if (!::android::base::WriteStringToFile(data.data(), file_path)) {
PLOG(WARNING) << "Failed to write cdev: " << cdev_name << " to " << data.data();
return false;
}
return true;
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <string>
#include <unordered_map>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
class ThermalFiles {
public:
ThermalFiles() = default;
~ThermalFiles() = default;
ThermalFiles(const ThermalFiles &) = delete;
void operator=(const ThermalFiles &) = delete;
std::string getThermalFilePath(std::string_view thermal_name) const;
// Returns true if add was successful, false otherwise.
bool addThermalFile(std::string_view thermal_name, std::string_view path);
// If thermal_name is not found in the thermal names to path map, this will set
// data to empty and return false. If the thermal_name is found and its content
// is read, this function will fill in data accordingly then return true.
bool readThermalFile(std::string_view thermal_name, std::string *data) const;
bool writeCdevFile(std::string_view thermal_name, std::string_view data);
size_t getNumThermalFiles() const { return thermal_name_to_path_map_.size(); }
private:
std::unordered_map<std::string, std::string> thermal_name_to_path_map_;
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

File diff suppressed because it is too large Load Diff

@ -0,0 +1,205 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/hardware/thermal/CoolingType.h>
#include <aidl/android/hardware/thermal/TemperatureType.h>
#include <aidl/android/hardware/thermal/ThrottlingSeverity.h>
#include <json/value.h>
#include <chrono>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <variant>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
constexpr size_t kThrottlingSeverityCount =
std::distance(::ndk::enum_range<ThrottlingSeverity>().begin(),
::ndk::enum_range<ThrottlingSeverity>().end());
using ThrottlingArray = std::array<float, static_cast<size_t>(kThrottlingSeverityCount)>;
using CdevArray = std::array<int, static_cast<size_t>(kThrottlingSeverityCount)>;
constexpr std::chrono::milliseconds kMinPollIntervalMs = std::chrono::milliseconds(2000);
constexpr std::chrono::milliseconds kUeventPollTimeoutMs = std::chrono::milliseconds(300000);
// Max number of time_in_state buckets is 20 in atoms
// VendorSensorCoolingDeviceStats, VendorTempResidencyStats
constexpr int kMaxStatsResidencyCount = 20;
constexpr int kMaxStatsThresholdCount = kMaxStatsResidencyCount - 1;
enum FormulaOption : uint32_t {
COUNT_THRESHOLD = 0,
WEIGHTED_AVG,
MAXIMUM,
MINIMUM,
};
template <typename T>
struct ThresholdList {
std::optional<std::string> logging_name;
std::vector<T> thresholds;
explicit ThresholdList(std::optional<std::string> logging_name, std::vector<T> thresholds)
: logging_name(logging_name), thresholds(thresholds) {}
ThresholdList() = default;
ThresholdList(const ThresholdList &) = default;
ThresholdList &operator=(const ThresholdList &) = default;
ThresholdList(ThresholdList &&) = default;
ThresholdList &operator=(ThresholdList &&) = default;
~ThresholdList() = default;
};
template <typename T>
struct StatsInfo {
// if bool, record all or none depending on flag
// if set, check name present in set
std::variant<bool, std::unordered_set<std::string> >
record_by_default_threshold_all_or_name_set_;
// map name to list of thresholds
std::unordered_map<std::string, std::vector<ThresholdList<T> > > record_by_threshold;
void clear() {
record_by_default_threshold_all_or_name_set_ = false;
record_by_threshold.clear();
}
};
struct StatsConfig {
StatsInfo<float> sensor_stats_info;
StatsInfo<int> cooling_device_request_info;
void clear() {
sensor_stats_info.clear();
cooling_device_request_info.clear();
}
};
enum SensorFusionType : uint32_t {
SENSOR = 0,
ODPM,
};
struct VirtualSensorInfo {
std::vector<std::string> linked_sensors;
std::vector<SensorFusionType> linked_sensors_type;
std::vector<float> coefficients;
float offset;
std::vector<std::string> trigger_sensors;
FormulaOption formula;
};
struct VirtualPowerRailInfo {
std::vector<std::string> linked_power_rails;
std::vector<float> coefficients;
float offset;
FormulaOption formula;
};
// The method when the ODPM power is lower than threshold
enum ReleaseLogic : uint32_t {
INCREASE = 0, // Increase throttling by step
DECREASE, // Decrease throttling by step
STEPWISE, // Support both increase and decrease logix
RELEASE_TO_FLOOR, // Release throttling to floor directly
NONE,
};
struct BindedCdevInfo {
CdevArray limit_info;
ThrottlingArray power_thresholds;
ReleaseLogic release_logic;
ThrottlingArray cdev_weight_for_pid;
CdevArray cdev_ceiling;
int max_release_step;
int max_throttle_step;
CdevArray cdev_floor_with_power_link;
std::string power_rail;
// The flag for activate release logic when power is higher than power threshold
bool high_power_check;
// The flag for only triggering throttling until all power samples are collected
bool throttling_with_power_link;
};
struct ThrottlingInfo {
ThrottlingArray k_po;
ThrottlingArray k_pu;
ThrottlingArray k_i;
ThrottlingArray k_d;
ThrottlingArray i_max;
ThrottlingArray max_alloc_power;
ThrottlingArray min_alloc_power;
ThrottlingArray s_power;
ThrottlingArray i_cutoff;
float i_default;
int tran_cycle;
std::unordered_map<std::string, ThrottlingArray> excluded_power_info_map;
std::unordered_map<std::string, BindedCdevInfo> binded_cdev_info_map;
};
struct SensorInfo {
TemperatureType type;
ThrottlingArray hot_thresholds;
ThrottlingArray cold_thresholds;
ThrottlingArray hot_hysteresis;
ThrottlingArray cold_hysteresis;
std::string temp_path;
float vr_threshold;
float multiplier;
std::chrono::milliseconds polling_delay;
std::chrono::milliseconds passive_delay;
std::chrono::milliseconds time_resolution;
bool send_cb;
bool send_powerhint;
bool is_watch;
bool is_hidden;
std::unique_ptr<VirtualSensorInfo> virtual_sensor_info;
std::shared_ptr<ThrottlingInfo> throttling_info;
};
struct CdevInfo {
CoolingType type;
std::string read_path;
std::string write_path;
std::vector<float> state2power;
int max_state;
};
struct PowerRailInfo {
std::string rail;
int power_sample_count;
std::chrono::milliseconds power_sample_delay;
std::unique_ptr<VirtualPowerRailInfo> virtual_power_rail_info;
};
bool ParseThermalConfig(std::string_view config_path, Json::Value *config);
bool ParseSensorInfo(const Json::Value &config,
std::unordered_map<std::string, SensorInfo> *sensors_parsed);
bool ParseCoolingDevice(const Json::Value &config,
std::unordered_map<std::string, CdevInfo> *cooling_device_parsed);
bool ParsePowerRailInfo(const Json::Value &config,
std::unordered_map<std::string, PowerRailInfo> *power_rail_parsed);
bool ParseStatsConfig(const Json::Value &config,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
StatsConfig *stats_config);
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,509 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "thermal_stats_helper.h"
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <algorithm>
#include <numeric>
#include <string_view>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
constexpr std::string_view kCustomThresholdSetSuffix("-TH-");
constexpr std::string_view kCompressedThresholdSuffix("-CMBN-TH");
using aidl::android::frameworks::stats::VendorAtom;
namespace PixelAtoms = ::android::hardware::google::pixel::PixelAtoms;
namespace {
static std::shared_ptr<IStats> stats_client = nullptr;
std::shared_ptr<IStats> getStatsService() {
static std::once_flag statsServiceFlag;
std::call_once(statsServiceFlag, []() {
const std::string instance = std::string() + IStats::descriptor + "/default";
bool isStatsDeclared = AServiceManager_isDeclared(instance.c_str());
if (!isStatsDeclared) {
LOG(ERROR) << "Stats service is not registered.";
return;
}
stats_client = IStats::fromBinder(
ndk::SpAIBinder(AServiceManager_waitForService(instance.c_str())));
});
return stats_client;
}
bool isRecordByDefaultThreshold(const std::variant<bool, std::unordered_set<std::string>>
&record_by_default_threshold_all_or_name_set_,
std::string_view name) {
if (std::holds_alternative<bool>(record_by_default_threshold_all_or_name_set_)) {
return std::get<bool>(record_by_default_threshold_all_or_name_set_);
}
return std::get<std::unordered_set<std::string>>(record_by_default_threshold_all_or_name_set_)
.count(name.data());
}
template <typename T>
int calculateThresholdBucket(const std::vector<T> &thresholds, T value) {
if (thresholds.empty()) {
LOG(VERBOSE) << "No threshold present, so bucket is " << value << " as int.";
return static_cast<int>(value);
}
auto threshold_idx = std::upper_bound(thresholds.begin(), thresholds.end(), value);
int bucket = (threshold_idx - thresholds.begin());
LOG(VERBOSE) << "For value: " << value << " bucket is: " << bucket;
return bucket;
}
} // namespace
bool ThermalStatsHelper::initializeStats(
const Json::Value &config,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_) {
StatsConfig stats_config;
if (!ParseStatsConfig(config, sensor_info_map_, cooling_device_info_map_, &stats_config)) {
LOG(ERROR) << "Failed to parse stats config";
return false;
}
bool is_initialized_ =
initializeSensorTempStats(stats_config.sensor_stats_info, sensor_info_map_) &&
initializeSensorCdevRequestStats(stats_config.cooling_device_request_info,
sensor_info_map_, cooling_device_info_map_);
if (is_initialized_) {
last_total_stats_report_time = boot_clock::now();
LOG(INFO) << "Thermal Stats Initialized Successfully";
}
return is_initialized_;
}
bool ThermalStatsHelper::initializeSensorCdevRequestStats(
const StatsInfo<int> &request_stats_info,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_) {
std::unique_lock<std::shared_mutex> _lock(sensor_cdev_request_stats_map_mutex_);
for (const auto &[sensor, sensor_info] : sensor_info_map_) {
for (const auto &binded_cdev_info_pair :
sensor_info.throttling_info->binded_cdev_info_map) {
const auto &cdev = binded_cdev_info_pair.first;
const auto &max_state =
cooling_device_info_map_.at(binded_cdev_info_pair.first).max_state;
// Record by all state
if (isRecordByDefaultThreshold(
request_stats_info.record_by_default_threshold_all_or_name_set_, cdev)) {
// if the number of states is greater / equal(as state starts from 0) than
// residency_buckets in atom combine the initial states
if (max_state >= kMaxStatsResidencyCount) {
// buckets = [max_state -kMaxStatsResidencyCount + 1, ...max_state]
// idx = [1, .. max_state - (max_state - kMaxStatsResidencyCount + 1) + 1]
// idx = [1, .. kMaxStatsResidencyCount]
const auto starting_state = max_state - kMaxStatsResidencyCount + 1;
std::vector<int> thresholds(kMaxStatsResidencyCount);
std::iota(thresholds.begin(), thresholds.end(), starting_state);
const auto logging_name = cdev + kCompressedThresholdSuffix.data();
ThresholdList<int> threshold_list(logging_name, thresholds);
sensor_cdev_request_stats_map_[sensor][cdev]
.stats_by_custom_threshold.emplace_back(threshold_list);
} else {
// buckets = [0, 1, 2, 3, ...max_state]
const auto default_threshold_time_in_state_size = max_state + 1;
sensor_cdev_request_stats_map_[sensor][cdev].stats_by_default_threshold =
StatsRecord(default_threshold_time_in_state_size);
}
LOG(INFO) << "Sensor Cdev user vote stats on basis of all state initialized for ["
<< sensor << "-" << cdev << "]";
}
// Record by custom threshold
if (request_stats_info.record_by_threshold.count(cdev)) {
for (const auto &threshold_list : request_stats_info.record_by_threshold.at(cdev)) {
// check last threshold value(which is >= number of buckets as numbers in
// threshold are strictly increasing from 0) is less than max_state
if (threshold_list.thresholds.back() >= max_state) {
LOG(ERROR) << "For sensor " << sensor << " bindedCdev: " << cdev
<< "Invalid bindedCdev stats threshold: "
<< threshold_list.thresholds.back() << " >= " << max_state;
sensor_cdev_request_stats_map_.clear();
return false;
}
sensor_cdev_request_stats_map_[sensor][cdev]
.stats_by_custom_threshold.emplace_back(threshold_list);
LOG(INFO)
<< "Sensor Cdev user vote stats on basis of threshold initialized for ["
<< sensor << "-" << cdev << "]";
}
}
}
}
return true;
}
bool ThermalStatsHelper::initializeSensorTempStats(
const StatsInfo<float> &sensor_stats_info,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_) {
std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
const int severity_time_in_state_size = kThrottlingSeverityCount;
for (const auto &[sensor, sensor_info] : sensor_info_map_) {
// Record by severity
if (sensor_info.is_watch &&
isRecordByDefaultThreshold(
sensor_stats_info.record_by_default_threshold_all_or_name_set_, sensor)) {
// number of buckets = number of severity
sensor_temp_stats_map_[sensor].stats_by_default_threshold =
StatsRecord(severity_time_in_state_size);
LOG(INFO) << "Sensor temp stats on basis of severity initialized for [" << sensor
<< "]";
}
// Record by custom threshold
if (sensor_stats_info.record_by_threshold.count(sensor)) {
for (const auto &threshold_list : sensor_stats_info.record_by_threshold.at(sensor)) {
sensor_temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back(
threshold_list);
LOG(INFO) << "Sensor temp stats on basis of threshold initialized for [" << sensor
<< "]";
}
}
}
return true;
}
void ThermalStatsHelper::updateStatsRecord(StatsRecord *stats_record, int new_state) {
const auto now = boot_clock::now();
const auto cur_state_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
now - stats_record->cur_state_start_time);
LOG(VERBOSE) << "Adding duration " << cur_state_duration.count()
<< " for cur_state: " << stats_record->cur_state << " with value: "
<< stats_record->time_in_state_ms[stats_record->cur_state].count();
// Update last record end time
stats_record->time_in_state_ms[stats_record->cur_state] += cur_state_duration;
stats_record->cur_state_start_time = now;
stats_record->cur_state = new_state;
}
void ThermalStatsHelper::updateSensorCdevRequestStats(std::string_view sensor,
std::string_view cdev, int new_value) {
std::unique_lock<std::shared_mutex> _lock(sensor_cdev_request_stats_map_mutex_);
if (!sensor_cdev_request_stats_map_.count(sensor.data()) ||
!sensor_cdev_request_stats_map_[sensor.data()].count(cdev.data())) {
return;
}
auto &request_stats = sensor_cdev_request_stats_map_[sensor.data()][cdev.data()];
for (auto &stats_by_threshold : request_stats.stats_by_custom_threshold) {
int value = calculateThresholdBucket(stats_by_threshold.thresholds, new_value);
if (value != stats_by_threshold.stats_record.cur_state) {
LOG(VERBOSE) << "Updating bindedCdev stats for sensor: " << sensor.data()
<< " , cooling_device: " << cdev.data() << " with new value: " << value;
updateStatsRecord(&stats_by_threshold.stats_record, value);
}
}
if (request_stats.stats_by_default_threshold.has_value()) {
auto &stats_record = request_stats.stats_by_default_threshold.value();
if (new_value != stats_record.cur_state) {
LOG(VERBOSE) << "Updating bindedCdev stats for sensor: " << sensor.data()
<< " , cooling_device: " << cdev.data()
<< " with new value: " << new_value;
updateStatsRecord(&stats_record, new_value);
}
}
}
void ThermalStatsHelper::updateSensorTempStatsByThreshold(std::string_view sensor,
float temperature) {
std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
if (!sensor_temp_stats_map_.count(sensor.data())) {
return;
}
auto &sensor_temp_stats = sensor_temp_stats_map_[sensor.data()];
for (auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) {
int value = calculateThresholdBucket(stats_by_threshold.thresholds, temperature);
if (value != stats_by_threshold.stats_record.cur_state) {
LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data()
<< " with value: " << value;
updateStatsRecord(&stats_by_threshold.stats_record, value);
}
}
if (temperature > sensor_temp_stats.max_temp) {
sensor_temp_stats.max_temp = temperature;
sensor_temp_stats.max_temp_timestamp = system_clock::now();
}
if (temperature < sensor_temp_stats.min_temp) {
sensor_temp_stats.min_temp = temperature;
sensor_temp_stats.min_temp_timestamp = system_clock::now();
}
}
void ThermalStatsHelper::updateSensorTempStatsBySeverity(std::string_view sensor,
const ThrottlingSeverity &severity) {
std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
if (sensor_temp_stats_map_.count(sensor.data()) &&
sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) {
auto &stats_record =
sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.value();
int value = static_cast<int>(severity);
if (value != stats_record.cur_state) {
LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data()
<< " with value: " << value;
updateStatsRecord(&stats_record, value);
}
}
}
int ThermalStatsHelper::reportStats() {
const auto curTime = boot_clock::now();
const auto since_last_total_stats_update_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(curTime -
last_total_stats_report_time);
LOG(VERBOSE) << "Duration from last total stats update is: "
<< since_last_total_stats_update_ms.count();
if (since_last_total_stats_update_ms < kUpdateIntervalMs) {
LOG(VERBOSE) << "Time elapsed since last update less than " << kUpdateIntervalMs.count();
return 0;
}
const std::shared_ptr<IStats> stats_client = getStatsService();
if (!stats_client) {
LOG(ERROR) << "Unable to get AIDL Stats service";
return -1;
}
int count_failed_reporting =
reportAllSensorTempStats(stats_client) + reportAllSensorCdevRequestStats(stats_client);
last_total_stats_report_time = curTime;
return count_failed_reporting;
}
int ThermalStatsHelper::reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client) {
int count_failed_reporting = 0;
std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
for (auto &[sensor, temp_stats] : sensor_temp_stats_map_) {
for (size_t threshold_set_idx = 0;
threshold_set_idx < temp_stats.stats_by_custom_threshold.size(); threshold_set_idx++) {
auto &stats_by_threshold = temp_stats.stats_by_custom_threshold[threshold_set_idx];
std::string sensor_name = stats_by_threshold.logging_name.value_or(
sensor + kCustomThresholdSetSuffix.data() + std::to_string(threshold_set_idx));
if (!reportSensorTempStats(stats_client, sensor_name, temp_stats,
&stats_by_threshold.stats_record)) {
count_failed_reporting++;
}
}
if (temp_stats.stats_by_default_threshold.has_value()) {
if (!reportSensorTempStats(stats_client, sensor, temp_stats,
&temp_stats.stats_by_default_threshold.value())) {
count_failed_reporting++;
}
}
}
return count_failed_reporting;
}
bool ThermalStatsHelper::reportSensorTempStats(const std::shared_ptr<IStats> &stats_client,
std::string_view sensor,
const SensorTempStats &sensor_temp_stats,
StatsRecord *stats_record) {
LOG(VERBOSE) << "Reporting sensor stats for " << sensor;
// maintain a copy in case reporting fails
StatsRecord thermal_stats_before_reporting = *stats_record;
std::vector<VendorAtomValue> values(2);
values[0].set<VendorAtomValue::stringValue>(sensor);
std::vector<int64_t> time_in_state_ms = processStatsRecordForReporting(stats_record);
const auto since_last_update_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
stats_record->cur_state_start_time - stats_record->last_stats_report_time);
values[1].set<VendorAtomValue::longValue>(since_last_update_ms.count());
VendorAtomValue tmp;
for (auto &time_in_state : time_in_state_ms) {
tmp.set<VendorAtomValue::longValue>(time_in_state);
values.push_back(tmp);
}
auto remaining_residency_buckets_count = kMaxStatsResidencyCount - time_in_state_ms.size();
if (remaining_residency_buckets_count > 0) {
tmp.set<VendorAtomValue::longValue>(0);
values.insert(values.end(), remaining_residency_buckets_count, tmp);
}
tmp.set<VendorAtomValue::floatValue>(sensor_temp_stats.max_temp);
values.push_back(tmp);
tmp.set<VendorAtomValue::longValue>(
system_clock::to_time_t(sensor_temp_stats.max_temp_timestamp));
values.push_back(tmp);
tmp.set<VendorAtomValue::floatValue>(sensor_temp_stats.min_temp);
values.push_back(tmp);
tmp.set<VendorAtomValue::longValue>(
system_clock::to_time_t(sensor_temp_stats.min_temp_timestamp));
values.push_back(tmp);
if (!reportAtom(stats_client, PixelAtoms::Atom::kVendorTempResidencyStats, std::move(values))) {
LOG(ERROR) << "Unable to report VendorTempResidencyStats to Stats service for "
"sensor: "
<< sensor;
*stats_record = restoreStatsRecordOnFailure(std::move(thermal_stats_before_reporting));
return false;
}
// Update last time of stats reporting
stats_record->last_stats_report_time = boot_clock::now();
return true;
}
int ThermalStatsHelper::reportAllSensorCdevRequestStats(
const std::shared_ptr<IStats> &stats_client) {
int count_failed_reporting = 0;
std::unique_lock<std::shared_mutex> _lock(sensor_cdev_request_stats_map_mutex_);
for (auto &[sensor, cdev_request_stats_map] : sensor_cdev_request_stats_map_) {
for (auto &[cdev, request_stats] : cdev_request_stats_map) {
for (size_t threshold_set_idx = 0;
threshold_set_idx < request_stats.stats_by_custom_threshold.size();
threshold_set_idx++) {
auto &stats_by_threshold =
request_stats.stats_by_custom_threshold[threshold_set_idx];
std::string cdev_name = stats_by_threshold.logging_name.value_or(
cdev + kCustomThresholdSetSuffix.data() +
std::to_string(threshold_set_idx));
if (!reportSensorCdevRequestStats(stats_client, sensor, cdev_name,
&stats_by_threshold.stats_record)) {
count_failed_reporting++;
}
}
if (request_stats.stats_by_default_threshold.has_value()) {
if (!reportSensorCdevRequestStats(
stats_client, sensor, cdev,
&request_stats.stats_by_default_threshold.value())) {
count_failed_reporting++;
}
}
}
}
return count_failed_reporting;
}
bool ThermalStatsHelper::reportSensorCdevRequestStats(const std::shared_ptr<IStats> &stats_client,
std::string_view sensor,
std::string_view cdev,
StatsRecord *stats_record) {
LOG(VERBOSE) << "Reporting bindedCdev stats for sensor: " << sensor
<< " cooling_device: " << cdev;
// maintain a copy in case reporting fails
StatsRecord thermal_stats_before_reporting = *stats_record;
std::vector<VendorAtomValue> values(3);
values[0].set<VendorAtomValue::stringValue>(sensor);
values[1].set<VendorAtomValue::stringValue>(cdev);
std::vector<int64_t> time_in_state_ms = processStatsRecordForReporting(stats_record);
const auto since_last_update_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
stats_record->cur_state_start_time - stats_record->last_stats_report_time);
values[2].set<VendorAtomValue::longValue>(since_last_update_ms.count());
VendorAtomValue tmp;
for (auto &time_in_state : time_in_state_ms) {
tmp.set<VendorAtomValue::longValue>(time_in_state);
values.push_back(tmp);
}
if (!reportAtom(stats_client, PixelAtoms::Atom::kVendorSensorCoolingDeviceStats,
std::move(values))) {
LOG(ERROR) << "Unable to report VendorSensorCoolingDeviceStats to Stats "
"service for sensor: "
<< sensor << " cooling_device: " << cdev;
*stats_record = restoreStatsRecordOnFailure(std::move(thermal_stats_before_reporting));
return false;
}
// Update last time of stats reporting
stats_record->last_stats_report_time = boot_clock::now();
return true;
}
std::vector<int64_t> ThermalStatsHelper::processStatsRecordForReporting(StatsRecord *stats_record) {
// update the last unclosed entry and start new record with same state
updateStatsRecord(stats_record, stats_record->cur_state);
std::vector<std::chrono::milliseconds> &time_in_state_ms = stats_record->time_in_state_ms;
// convert std::chrono::milliseconds time_in_state to int64_t vector for reporting
std::vector<int64_t> stats_residency(time_in_state_ms.size());
std::transform(time_in_state_ms.begin(), time_in_state_ms.end(), stats_residency.begin(),
[](std::chrono::milliseconds time_ms) { return time_ms.count(); });
// clear previous stats
std::fill(time_in_state_ms.begin(), time_in_state_ms.end(), std::chrono::milliseconds::zero());
return stats_residency;
}
bool ThermalStatsHelper::reportAtom(const std::shared_ptr<IStats> &stats_client,
const int32_t &atom_id, std::vector<VendorAtomValue> &&values) {
LOG(VERBOSE) << "Reporting thermal stats for atom_id " << atom_id;
// Send vendor atom to IStats HAL
VendorAtom event = {.reverseDomainName = "", .atomId = atom_id, .values = std::move(values)};
const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
return ret.isOk();
}
StatsRecord ThermalStatsHelper::restoreStatsRecordOnFailure(
StatsRecord &&stats_record_before_failure) {
stats_record_before_failure.report_fail_count += 1;
// If consecutive count of failure is high, reset stat to avoid overflow
if (stats_record_before_failure.report_fail_count >= kMaxStatsReportingFailCount) {
return StatsRecord(stats_record_before_failure.time_in_state_ms.size(),
stats_record_before_failure.cur_state);
} else {
return stats_record_before_failure;
}
}
std::unordered_map<std::string, SensorTempStats> ThermalStatsHelper::GetSensorTempStatsSnapshot() {
auto sensor_temp_stats_snapshot = sensor_temp_stats_map_;
for (auto &sensor_temp_stats_pair : sensor_temp_stats_snapshot) {
for (auto &temp_stats : sensor_temp_stats_pair.second.stats_by_custom_threshold) {
// update the last unclosed entry and start new record with same state
updateStatsRecord(&temp_stats.stats_record, temp_stats.stats_record.cur_state);
}
if (sensor_temp_stats_pair.second.stats_by_default_threshold.has_value()) {
auto &stats_by_default_threshold =
sensor_temp_stats_pair.second.stats_by_default_threshold.value();
// update the last unclosed entry and start new record with same state
updateStatsRecord(&stats_by_default_threshold, stats_by_default_threshold.cur_state);
}
}
return sensor_temp_stats_snapshot;
}
std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
ThermalStatsHelper::GetSensorCoolingDeviceRequestStatsSnapshot() {
auto sensor_cdev_request_stats_snapshot = sensor_cdev_request_stats_map_;
for (auto &sensor_cdev_request_stats_pair : sensor_cdev_request_stats_snapshot) {
for (auto &cdev_request_stats_pair : sensor_cdev_request_stats_pair.second) {
for (auto &request_stats : cdev_request_stats_pair.second.stats_by_custom_threshold) {
// update the last unclosed entry and start new record with same state
updateStatsRecord(&request_stats.stats_record,
request_stats.stats_record.cur_state);
}
if (cdev_request_stats_pair.second.stats_by_default_threshold.has_value()) {
auto &stats_by_default_threshold =
cdev_request_stats_pair.second.stats_by_default_threshold.value();
// update the last unclosed entry and start new record with same state
updateStatsRecord(&stats_by_default_threshold,
stats_by_default_threshold.cur_state);
}
}
}
return sensor_cdev_request_stats_snapshot;
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,170 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/frameworks/stats/IStats.h>
#include <aidl/android/hardware/thermal/Temperature.h>
#include <android-base/chrono_utils.h>
#include <chrono>
#include <shared_mutex>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "thermal_info.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using aidl::android::frameworks::stats::IStats;
using aidl::android::frameworks::stats::VendorAtomValue;
using ::android::base::boot_clock;
using std::chrono::system_clock;
using SystemTimePoint = std::chrono::time_point<std::chrono::system_clock>;
constexpr int kMaxStatsReportingFailCount = 3;
struct StatsRecord {
int cur_state; /* temperature / cdev state at current time */
boot_clock::time_point cur_state_start_time;
boot_clock::time_point last_stats_report_time = boot_clock::time_point::min();
std::vector<std::chrono::milliseconds> time_in_state_ms; /* stats array */
int report_fail_count = 0; /* Number of times failed to report stats */
explicit StatsRecord(const size_t &time_in_state_size, int state = 0)
: cur_state(state),
cur_state_start_time(boot_clock::now()),
last_stats_report_time(boot_clock::now()),
report_fail_count(0) {
time_in_state_ms = std::vector<std::chrono::milliseconds>(
time_in_state_size, std::chrono::milliseconds::zero());
}
StatsRecord() = default;
StatsRecord(const StatsRecord &) = default;
StatsRecord &operator=(const StatsRecord &) = default;
StatsRecord(StatsRecord &&) = default;
StatsRecord &operator=(StatsRecord &&) = default;
~StatsRecord() = default;
};
template <typename ValueType>
struct StatsByThreshold {
std::vector<ValueType> thresholds;
std::optional<std::string> logging_name;
StatsRecord stats_record;
explicit StatsByThreshold(ThresholdList<ValueType> threshold_list)
: thresholds(threshold_list.thresholds), logging_name(threshold_list.logging_name) {
// number of states = number of thresholds + 1
// e.g. threshold: [30, 50, 60]
// buckets: [MIN - 30, 30 - 50, 50-60, 60-MAX]
int time_in_state_size = threshold_list.thresholds.size() + 1;
stats_record = StatsRecord(time_in_state_size);
}
StatsByThreshold() = default;
StatsByThreshold(const StatsByThreshold &) = default;
StatsByThreshold &operator=(const StatsByThreshold &) = default;
StatsByThreshold(StatsByThreshold &&) = default;
StatsByThreshold &operator=(StatsByThreshold &&) = default;
~StatsByThreshold() = default;
};
template <typename ValueType>
struct ThermalStats {
std::vector<StatsByThreshold<ValueType>> stats_by_custom_threshold;
std::optional<StatsRecord> stats_by_default_threshold;
};
struct SensorTempStats : ThermalStats<float> {
float max_temp = std::numeric_limits<float>::min();
SystemTimePoint max_temp_timestamp = SystemTimePoint::min();
float min_temp = std::numeric_limits<float>::max();
SystemTimePoint min_temp_timestamp = SystemTimePoint::min();
};
class ThermalStatsHelper {
public:
ThermalStatsHelper() = default;
~ThermalStatsHelper() = default;
// Disallow copy and assign
ThermalStatsHelper(const ThermalStatsHelper &) = delete;
void operator=(const ThermalStatsHelper &) = delete;
bool initializeStats(const Json::Value &config,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_);
void updateSensorCdevRequestStats(std::string_view trigger_sensor, std::string_view cdev,
int new_state);
void updateSensorTempStatsBySeverity(std::string_view sensor,
const ThrottlingSeverity &severity);
void updateSensorTempStatsByThreshold(std::string_view sensor, float temperature);
/*
* Function to report all the stats by calling all specific stats reporting function.
* Returns:
* 0, if time_elapsed < kUpdateIntervalMs or if no failure in reporting
* -1, if failed to get AIDL stats services
* >0, count represents the number of stats failed to report.
*/
int reportStats();
// Get a snapshot of Thermal Stats Sensor Map till that point in time
std::unordered_map<std::string, SensorTempStats> GetSensorTempStatsSnapshot();
// Get a snapshot of Thermal Stats Sensor Map till that point in time
std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
GetSensorCoolingDeviceRequestStatsSnapshot();
private:
static constexpr std::chrono::milliseconds kUpdateIntervalMs =
std::chrono::duration_cast<std::chrono::milliseconds>(24h);
boot_clock::time_point last_total_stats_report_time = boot_clock::time_point::min();
mutable std::shared_mutex sensor_temp_stats_map_mutex_;
// Temperature stats for each sensor being watched
std::unordered_map<std::string, SensorTempStats> sensor_temp_stats_map_;
mutable std::shared_mutex sensor_cdev_request_stats_map_mutex_;
// userVote request stat for the sensor to the corresponding cdev (sensor -> cdev ->
// StatsRecord)
std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
sensor_cdev_request_stats_map_;
bool initializeSensorTempStats(
const StatsInfo<float> &sensor_stats_info,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_);
bool initializeSensorCdevRequestStats(
const StatsInfo<int> &request_stats_info,
const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_);
void updateStatsRecord(StatsRecord *stats_record, int new_state);
int reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client);
bool reportSensorTempStats(const std::shared_ptr<IStats> &stats_client, std::string_view sensor,
const SensorTempStats &sensor_temp_stats, StatsRecord *stats_record);
int reportAllSensorCdevRequestStats(const std::shared_ptr<IStats> &stats_client);
bool reportSensorCdevRequestStats(const std::shared_ptr<IStats> &stats_client,
std::string_view sensor, std::string_view cdev,
StatsRecord *stats_record);
bool reportAtom(const std::shared_ptr<IStats> &stats_client, const int32_t &atom_id,
std::vector<VendorAtomValue> &&values);
std::vector<int64_t> processStatsRecordForReporting(StatsRecord *stats_record);
StatsRecord restoreStatsRecordOnFailure(StatsRecord &&stats_record_before_failure);
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,767 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
#include "thermal_throttling.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <utils/Trace.h>
#include <iterator>
#include <set>
#include <sstream>
#include <thread>
#include <vector>
#include "power_files.h"
#include "thermal_info.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::android::base::StringPrintf;
// To find the next PID target state according to the current thermal severity
size_t getTargetStateOfPID(const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity) {
size_t target_state = 0;
for (const auto &severity : ::ndk::enum_range<ThrottlingSeverity>()) {
size_t state = static_cast<size_t>(severity);
if (std::isnan(sensor_info.throttling_info->s_power[state])) {
continue;
}
target_state = state;
if (severity > curr_severity) {
break;
}
}
LOG(VERBOSE) << "PID target state = " << target_state;
return target_state;
}
void ThermalThrottling::clearThrottlingData(std::string_view sensor_name,
const SensorInfo &sensor_info) {
if (!thermal_throttling_status_map_.count(sensor_name.data())) {
return;
}
std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
for (auto &pid_power_budget_pair :
thermal_throttling_status_map_.at(sensor_name.data()).pid_power_budget_map) {
pid_power_budget_pair.second = std::numeric_limits<int>::max();
}
for (auto &pid_cdev_request_pair :
thermal_throttling_status_map_.at(sensor_name.data()).pid_cdev_request_map) {
pid_cdev_request_pair.second = 0;
}
for (auto &hardlimit_cdev_request_pair :
thermal_throttling_status_map_.at(sensor_name.data()).hardlimit_cdev_request_map) {
hardlimit_cdev_request_pair.second = 0;
}
for (auto &throttling_release_pair :
thermal_throttling_status_map_.at(sensor_name.data()).throttling_release_map) {
throttling_release_pair.second = 0;
}
thermal_throttling_status_map_[sensor_name.data()].prev_err = NAN;
thermal_throttling_status_map_[sensor_name.data()].i_budget =
sensor_info.throttling_info->i_default;
thermal_throttling_status_map_[sensor_name.data()].prev_target =
static_cast<size_t>(ThrottlingSeverity::NONE);
thermal_throttling_status_map_[sensor_name.data()].prev_power_budget = NAN;
thermal_throttling_status_map_[sensor_name.data()].tran_cycle = 0;
return;
}
bool ThermalThrottling::registerThermalThrottling(
std::string_view sensor_name, const std::shared_ptr<ThrottlingInfo> &throttling_info,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
if (thermal_throttling_status_map_.count(sensor_name.data())) {
LOG(ERROR) << "Sensor " << sensor_name.data() << " throttling map has been registered";
return false;
}
if (throttling_info == nullptr) {
LOG(ERROR) << "Sensor " << sensor_name.data() << " has no throttling info";
return false;
}
thermal_throttling_status_map_[sensor_name.data()].prev_err = NAN;
thermal_throttling_status_map_[sensor_name.data()].i_budget = throttling_info->i_default;
thermal_throttling_status_map_[sensor_name.data()].prev_target =
static_cast<size_t>(ThrottlingSeverity::NONE);
thermal_throttling_status_map_[sensor_name.data()].prev_power_budget = NAN;
thermal_throttling_status_map_[sensor_name.data()].tran_cycle = 0;
for (auto &binded_cdev_pair : throttling_info->binded_cdev_info_map) {
if (!cooling_device_info_map.count(binded_cdev_pair.first)) {
LOG(ERROR) << "Could not find " << sensor_name.data() << "'s binded CDEV "
<< binded_cdev_pair.first;
return false;
}
// Register PID throttling map
for (const auto &cdev_weight : binded_cdev_pair.second.cdev_weight_for_pid) {
if (!std::isnan(cdev_weight)) {
thermal_throttling_status_map_[sensor_name.data()]
.pid_power_budget_map[binded_cdev_pair.first] =
std::numeric_limits<int>::max();
thermal_throttling_status_map_[sensor_name.data()]
.pid_cdev_request_map[binded_cdev_pair.first] = 0;
thermal_throttling_status_map_[sensor_name.data()]
.cdev_status_map[binded_cdev_pair.first] = 0;
cdev_all_request_map_[binded_cdev_pair.first].insert(0);
break;
}
}
// Register hard limit throttling map
for (const auto &limit_info : binded_cdev_pair.second.limit_info) {
if (limit_info > 0) {
thermal_throttling_status_map_[sensor_name.data()]
.hardlimit_cdev_request_map[binded_cdev_pair.first] = 0;
thermal_throttling_status_map_[sensor_name.data()]
.cdev_status_map[binded_cdev_pair.first] = 0;
cdev_all_request_map_[binded_cdev_pair.first].insert(0);
break;
}
}
// Register throttling release map if power threshold exists
if (!binded_cdev_pair.second.power_rail.empty()) {
for (const auto &power_threshold : binded_cdev_pair.second.power_thresholds) {
if (!std::isnan(power_threshold)) {
thermal_throttling_status_map_[sensor_name.data()]
.throttling_release_map[binded_cdev_pair.first] = 0;
break;
}
}
}
}
return true;
}
// return power budget based on PID algo
float ThermalThrottling::updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info,
std::chrono::milliseconds time_elapsed_ms,
ThrottlingSeverity curr_severity) {
float p = 0, d = 0;
float power_budget = std::numeric_limits<float>::max();
bool target_changed = false;
float budget_transient = 0.0;
auto &throttling_status = thermal_throttling_status_map_.at(temp.name);
std::string sensor_name = temp.name;
if (curr_severity == ThrottlingSeverity::NONE) {
return power_budget;
}
const auto target_state = getTargetStateOfPID(sensor_info, curr_severity);
if (throttling_status.prev_target != static_cast<size_t>(ThrottlingSeverity::NONE) &&
target_state != throttling_status.prev_target &&
sensor_info.throttling_info->tran_cycle > 0) {
throttling_status.tran_cycle = sensor_info.throttling_info->tran_cycle - 1;
target_changed = true;
}
throttling_status.prev_target = target_state;
// Compute PID
float err = sensor_info.hot_thresholds[target_state] - temp.value;
p = err * (err < 0 ? sensor_info.throttling_info->k_po[target_state]
: sensor_info.throttling_info->k_pu[target_state]);
if (err < sensor_info.throttling_info->i_cutoff[target_state]) {
throttling_status.i_budget += err * sensor_info.throttling_info->k_i[target_state];
}
if (fabsf(throttling_status.i_budget) > sensor_info.throttling_info->i_max[target_state]) {
throttling_status.i_budget = sensor_info.throttling_info->i_max[target_state] *
(throttling_status.i_budget > 0 ? 1 : -1);
}
if (!std::isnan(throttling_status.prev_err) &&
time_elapsed_ms != std::chrono::milliseconds::zero()) {
d = sensor_info.throttling_info->k_d[target_state] * (err - throttling_status.prev_err) /
time_elapsed_ms.count();
}
throttling_status.prev_err = err;
// Calculate power budget
power_budget =
sensor_info.throttling_info->s_power[target_state] + p + throttling_status.i_budget + d;
if (power_budget < sensor_info.throttling_info->min_alloc_power[target_state]) {
power_budget = sensor_info.throttling_info->min_alloc_power[target_state];
}
if (power_budget > sensor_info.throttling_info->max_alloc_power[target_state]) {
power_budget = sensor_info.throttling_info->max_alloc_power[target_state];
}
if (target_changed) {
throttling_status.budget_transient = throttling_status.prev_power_budget - power_budget;
}
if (throttling_status.tran_cycle) {
budget_transient = throttling_status.budget_transient *
((static_cast<float>(throttling_status.tran_cycle) /
static_cast<float>(sensor_info.throttling_info->tran_cycle)));
power_budget += budget_transient;
throttling_status.tran_cycle--;
}
LOG(INFO) << temp.name << " power_budget=" << power_budget << " err=" << err
<< " s_power=" << sensor_info.throttling_info->s_power[target_state]
<< " time_elapsed_ms=" << time_elapsed_ms.count() << " p=" << p
<< " i=" << throttling_status.i_budget << " d=" << d
<< " budget transient=" << budget_transient << " control target=" << target_state;
ATRACE_INT((sensor_name + std::string("-power_budget")).c_str(),
static_cast<int>(power_budget));
ATRACE_INT((sensor_name + std::string("-s_power")).c_str(),
static_cast<int>(sensor_info.throttling_info->s_power[target_state]));
ATRACE_INT((sensor_name + std::string("-time_elapsed_ms")).c_str(),
static_cast<int>(time_elapsed_ms.count()));
ATRACE_INT((sensor_name + std::string("-budget_transient")).c_str(),
static_cast<int>(budget_transient));
ATRACE_INT((sensor_name + std::string("-i")).c_str(),
static_cast<int>(throttling_status.i_budget));
ATRACE_INT((sensor_name + std::string("-target_state")).c_str(),
static_cast<int>(target_state));
ATRACE_INT((sensor_name + std::string("-err")).c_str(), static_cast<int>(err / sensor_info.multiplier));
ATRACE_INT((sensor_name + std::string("-p")).c_str(), static_cast<int>(p));
ATRACE_INT((sensor_name + std::string("-d")).c_str(), static_cast<int>(d));
ATRACE_INT((sensor_name + std::string("-temp")).c_str(), static_cast<int>(temp.value / sensor_info.multiplier));
throttling_status.prev_power_budget = power_budget;
return power_budget;
}
float ThermalThrottling::computeExcludedPower(
const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity,
const std::unordered_map<std::string, PowerStatus> &power_status_map, std::string *log_buf,
std::string_view sensor_name) {
float excluded_power = 0.0;
for (const auto &excluded_power_info_pair :
sensor_info.throttling_info->excluded_power_info_map) {
const auto last_updated_avg_power =
power_status_map.at(excluded_power_info_pair.first).last_updated_avg_power;
if (!std::isnan(last_updated_avg_power)) {
excluded_power += last_updated_avg_power *
excluded_power_info_pair.second[static_cast<size_t>(curr_severity)];
log_buf->append(StringPrintf(
"(%s: %0.2f mW, cdev_weight: %f)", excluded_power_info_pair.first.c_str(),
last_updated_avg_power,
excluded_power_info_pair.second[static_cast<size_t>(curr_severity)]));
ATRACE_INT((std::string(sensor_name) + std::string("-") +
excluded_power_info_pair.first + std::string("-avg_power"))
.c_str(),
static_cast<int>(last_updated_avg_power));
}
}
ATRACE_INT((std::string(sensor_name) + std::string("-excluded_power")).c_str(),
static_cast<int>(excluded_power));
return excluded_power;
}
// Allocate power budget to binded cooling devices base on the real ODPM power data
bool ThermalThrottling::allocatePowerToCdev(
const Temperature &temp, const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
float total_weight = 0;
float last_updated_avg_power = NAN;
float allocated_power = 0;
float allocated_weight = 0;
bool low_power_device_check = true;
bool is_budget_allocated = false;
bool power_data_invalid = false;
std::set<std::string> allocated_cdev;
std::string log_buf;
std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
auto total_power_budget = updatePowerBudget(temp, sensor_info, time_elapsed_ms, curr_severity);
if (sensor_info.throttling_info->excluded_power_info_map.size()) {
total_power_budget -= computeExcludedPower(sensor_info, curr_severity, power_status_map,
&log_buf, temp.name);
total_power_budget = std::max(total_power_budget, 0.0f);
if (!log_buf.empty()) {
LOG(INFO) << temp.name << " power budget=" << total_power_budget << " after " << log_buf
<< " is excluded";
}
}
// Compute total cdev weight
for (const auto &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) {
const auto cdev_weight = binded_cdev_info_pair.second
.cdev_weight_for_pid[static_cast<size_t>(curr_severity)];
if (std::isnan(cdev_weight) || cdev_weight == 0) {
allocated_cdev.insert(binded_cdev_info_pair.first);
continue;
}
total_weight += cdev_weight;
}
while (!is_budget_allocated) {
for (const auto &binded_cdev_info_pair :
sensor_info.throttling_info->binded_cdev_info_map) {
float cdev_power_adjustment = 0;
const auto cdev_weight =
binded_cdev_info_pair.second
.cdev_weight_for_pid[static_cast<size_t>(curr_severity)];
if (allocated_cdev.count(binded_cdev_info_pair.first)) {
continue;
}
if (std::isnan(cdev_weight) || !cdev_weight) {
allocated_cdev.insert(binded_cdev_info_pair.first);
continue;
}
// Get the power data
if (!power_data_invalid) {
if (!binded_cdev_info_pair.second.power_rail.empty()) {
last_updated_avg_power =
power_status_map.at(binded_cdev_info_pair.second.power_rail)
.last_updated_avg_power;
if (std::isnan(last_updated_avg_power)) {
LOG(VERBOSE) << "power data is under collecting";
power_data_invalid = true;
break;
}
ATRACE_INT((temp.name + std::string("-") +
binded_cdev_info_pair.second.power_rail + std::string("-avg_power"))
.c_str(),
static_cast<int>(last_updated_avg_power));
} else {
power_data_invalid = true;
break;
}
if (binded_cdev_info_pair.second.throttling_with_power_link) {
return false;
}
}
auto cdev_power_budget = total_power_budget * (cdev_weight / total_weight);
cdev_power_adjustment = cdev_power_budget - last_updated_avg_power;
if (low_power_device_check) {
// Share the budget for the CDEV which power is lower than target
if (cdev_power_adjustment > 0 &&
thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
binded_cdev_info_pair.first) == 0) {
allocated_power += last_updated_avg_power;
allocated_weight += cdev_weight;
allocated_cdev.insert(binded_cdev_info_pair.first);
if (!binded_cdev_info_pair.second.power_rail.empty()) {
log_buf.append(StringPrintf("(%s: %0.2f mW)",
binded_cdev_info_pair.second.power_rail.c_str(),
last_updated_avg_power));
}
LOG(VERBOSE) << temp.name << " binded " << binded_cdev_info_pair.first
<< " has been already at min state 0";
}
} else {
const CdevInfo &cdev_info = cooling_device_info_map.at(binded_cdev_info_pair.first);
if (!binded_cdev_info_pair.second.power_rail.empty()) {
log_buf.append(StringPrintf("(%s: %0.2f mW)",
binded_cdev_info_pair.second.power_rail.c_str(),
last_updated_avg_power));
}
// Ignore the power distribution if the CDEV has no space to reduce power
if ((cdev_power_adjustment < 0 &&
thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
binded_cdev_info_pair.first) == cdev_info.max_state)) {
LOG(VERBOSE) << temp.name << " binded " << binded_cdev_info_pair.first
<< " has been already at max state " << cdev_info.max_state;
continue;
}
if (!power_data_invalid && binded_cdev_info_pair.second.power_rail != "") {
auto cdev_curr_power_budget =
thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
binded_cdev_info_pair.first);
if (last_updated_avg_power > cdev_curr_power_budget) {
cdev_power_budget = cdev_curr_power_budget +=
(cdev_power_adjustment *
(cdev_curr_power_budget / last_updated_avg_power));
} else {
cdev_power_budget = cdev_curr_power_budget += cdev_power_adjustment;
}
} else {
cdev_power_budget = total_power_budget * (cdev_weight / total_weight);
}
if (!std::isnan(cdev_info.state2power[0]) &&
cdev_power_budget > cdev_info.state2power[0]) {
cdev_power_budget = cdev_info.state2power[0];
} else if (cdev_power_budget < 0) {
cdev_power_budget = 0;
}
int max_cdev_vote;
if (!getCdevMaxRequest(binded_cdev_info_pair.first, &max_cdev_vote)) {
return false;
}
const auto curr_cdev_vote =
thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
binded_cdev_info_pair.first);
if (binded_cdev_info_pair.second.max_release_step !=
std::numeric_limits<int>::max() &&
(power_data_invalid || cdev_power_adjustment > 0)) {
if (!power_data_invalid && curr_cdev_vote < max_cdev_vote) {
cdev_power_budget = cdev_info.state2power[curr_cdev_vote];
LOG(VERBOSE) << temp.name << "'s " << binded_cdev_info_pair.first
<< " vote: " << curr_cdev_vote
<< " is lower than max cdev vote: " << max_cdev_vote;
} else {
const auto target_state = std::max(
curr_cdev_vote - binded_cdev_info_pair.second.max_release_step, 0);
cdev_power_budget =
std::min(cdev_power_budget, cdev_info.state2power[target_state]);
}
}
if (binded_cdev_info_pair.second.max_throttle_step !=
std::numeric_limits<int>::max() &&
(power_data_invalid || cdev_power_adjustment < 0)) {
const auto target_state = std::min(
curr_cdev_vote + binded_cdev_info_pair.second.max_throttle_step,
cdev_info.max_state);
cdev_power_budget =
std::max(cdev_power_budget, cdev_info.state2power[target_state]);
}
thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
binded_cdev_info_pair.first) = cdev_power_budget;
LOG(VERBOSE) << temp.name << " allocate "
<< thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
binded_cdev_info_pair.first)
<< "mW to " << binded_cdev_info_pair.first
<< "(cdev_weight=" << cdev_weight << ")";
}
}
if (!power_data_invalid) {
total_power_budget -= allocated_power;
total_weight -= allocated_weight;
}
allocated_power = 0;
allocated_weight = 0;
if (low_power_device_check) {
low_power_device_check = false;
} else {
is_budget_allocated = true;
}
}
if (log_buf.size()) {
LOG(INFO) << temp.name << " binded power rails: " << log_buf;
}
return true;
}
void ThermalThrottling::updateCdevRequestByPower(
std::string sensor_name,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
size_t i;
std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
for (auto &pid_power_budget_pair :
thermal_throttling_status_map_[sensor_name.data()].pid_power_budget_map) {
const CdevInfo &cdev_info = cooling_device_info_map.at(pid_power_budget_pair.first);
for (i = 0; i < cdev_info.state2power.size() - 1; ++i) {
if (pid_power_budget_pair.second >= cdev_info.state2power[i]) {
break;
}
}
thermal_throttling_status_map_[sensor_name.data()].pid_cdev_request_map.at(
pid_power_budget_pair.first) = static_cast<int>(i);
}
return;
}
void ThermalThrottling::updateCdevRequestBySeverity(std::string_view sensor_name,
const SensorInfo &sensor_info,
ThrottlingSeverity curr_severity) {
std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
for (auto const &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) {
thermal_throttling_status_map_[sensor_name.data()].hardlimit_cdev_request_map.at(
binded_cdev_info_pair.first) =
binded_cdev_info_pair.second.limit_info[static_cast<size_t>(curr_severity)];
LOG(VERBOSE) << "Hard Limit: Sensor " << sensor_name.data() << " update cdev "
<< binded_cdev_info_pair.first << " to "
<< thermal_throttling_status_map_[sensor_name.data()]
.hardlimit_cdev_request_map.at(binded_cdev_info_pair.first);
}
}
bool ThermalThrottling::throttlingReleaseUpdate(
std::string_view sensor_name,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
const ThrottlingSeverity severity, const SensorInfo &sensor_info) {
ATRACE_CALL();
std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
if (!thermal_throttling_status_map_.count(sensor_name.data())) {
return false;
}
auto &thermal_throttling_status = thermal_throttling_status_map_.at(sensor_name.data());
for (const auto &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) {
float avg_power = -1;
if (!thermal_throttling_status.throttling_release_map.count(binded_cdev_info_pair.first) ||
!power_status_map.count(binded_cdev_info_pair.second.power_rail)) {
return false;
}
const auto max_state = cooling_device_info_map.at(binded_cdev_info_pair.first).max_state;
auto &release_step =
thermal_throttling_status.throttling_release_map.at(binded_cdev_info_pair.first);
avg_power =
power_status_map.at(binded_cdev_info_pair.second.power_rail).last_updated_avg_power;
if (std::isnan(avg_power) || avg_power < 0) {
release_step = binded_cdev_info_pair.second.throttling_with_power_link ? max_state : 0;
continue;
}
bool is_over_budget = true;
if (!binded_cdev_info_pair.second.high_power_check) {
if (avg_power <
binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]) {
is_over_budget = false;
}
} else {
if (avg_power >
binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]) {
is_over_budget = false;
}
}
LOG(INFO) << sensor_name.data() << "'s " << binded_cdev_info_pair.first
<< " binded power rail " << binded_cdev_info_pair.second.power_rail
<< ": power threshold = "
<< binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]
<< ", avg power = " << avg_power;
std::string atrace_prefix = ::android::base::StringPrintf(
"%s-%s", sensor_name.data(), binded_cdev_info_pair.second.power_rail.data());
ATRACE_INT(
(atrace_prefix + std::string("-power_threshold")).c_str(),
static_cast<int>(
binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]));
ATRACE_INT((atrace_prefix + std::string("-avg_power")).c_str(), avg_power);
switch (binded_cdev_info_pair.second.release_logic) {
case ReleaseLogic::INCREASE:
if (!is_over_budget) {
if (std::abs(release_step) < static_cast<int>(max_state)) {
release_step--;
}
} else {
release_step = 0;
}
break;
case ReleaseLogic::DECREASE:
if (!is_over_budget) {
if (release_step < static_cast<int>(max_state)) {
release_step++;
}
} else {
release_step = 0;
}
break;
case ReleaseLogic::STEPWISE:
if (!is_over_budget) {
if (release_step < static_cast<int>(max_state)) {
release_step++;
}
} else {
if (std::abs(release_step) < static_cast<int>(max_state)) {
release_step--;
}
}
break;
case ReleaseLogic::RELEASE_TO_FLOOR:
release_step = is_over_budget ? 0 : max_state;
break;
case ReleaseLogic::NONE:
default:
break;
}
}
return true;
}
void ThermalThrottling::thermalThrottlingUpdate(
const Temperature &temp, const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
if (!thermal_throttling_status_map_.count(temp.name)) {
return;
}
if (thermal_throttling_status_map_[temp.name].pid_power_budget_map.size()) {
if (!allocatePowerToCdev(temp, sensor_info, curr_severity, time_elapsed_ms,
power_status_map, cooling_device_info_map)) {
LOG(ERROR) << "Sensor " << temp.name << " PID request cdev failed";
// Clear the CDEV request if the power budget is failed to be allocated
for (auto &pid_cdev_request_pair :
thermal_throttling_status_map_[temp.name].pid_cdev_request_map) {
pid_cdev_request_pair.second = 0;
}
}
updateCdevRequestByPower(temp.name, cooling_device_info_map);
}
if (thermal_throttling_status_map_[temp.name].hardlimit_cdev_request_map.size()) {
updateCdevRequestBySeverity(temp.name.c_str(), sensor_info, curr_severity);
}
if (thermal_throttling_status_map_[temp.name].throttling_release_map.size()) {
throttlingReleaseUpdate(temp.name.c_str(), cooling_device_info_map, power_status_map,
curr_severity, sensor_info);
}
}
void ThermalThrottling::computeCoolingDevicesRequest(
std::string_view sensor_name, const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity, std::vector<std::string> *cooling_devices_to_update,
ThermalStatsHelper *thermal_stats_helper) {
int release_step = 0;
std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
if (!thermal_throttling_status_map_.count(sensor_name.data())) {
return;
}
auto &thermal_throttling_status = thermal_throttling_status_map_.at(sensor_name.data());
const auto &cdev_release_map = thermal_throttling_status.throttling_release_map;
for (auto &cdev_request_pair : thermal_throttling_status.cdev_status_map) {
int pid_cdev_request = 0;
int hardlimit_cdev_request = 0;
const auto &cdev_name = cdev_request_pair.first;
const auto &binded_cdev_info =
sensor_info.throttling_info->binded_cdev_info_map.at(cdev_name);
const auto cdev_ceiling = binded_cdev_info.cdev_ceiling[static_cast<size_t>(curr_severity)];
const auto cdev_floor =
binded_cdev_info.cdev_floor_with_power_link[static_cast<size_t>(curr_severity)];
release_step = 0;
if (thermal_throttling_status.pid_cdev_request_map.count(cdev_name)) {
pid_cdev_request = thermal_throttling_status.pid_cdev_request_map.at(cdev_name);
}
if (thermal_throttling_status.hardlimit_cdev_request_map.count(cdev_name)) {
hardlimit_cdev_request =
thermal_throttling_status.hardlimit_cdev_request_map.at(cdev_name);
}
if (cdev_release_map.count(cdev_name)) {
release_step = cdev_release_map.at(cdev_name);
}
LOG(VERBOSE) << sensor_name.data() << " binded cooling device " << cdev_name
<< "'s pid_request=" << pid_cdev_request
<< " hardlimit_cdev_request=" << hardlimit_cdev_request
<< " release_step=" << release_step
<< " cdev_floor_with_power_link=" << cdev_floor
<< " cdev_ceiling=" << cdev_ceiling;
std::string atrace_prefix =
::android::base::StringPrintf("%s-%s", sensor_name.data(), cdev_name.data());
ATRACE_INT((atrace_prefix + std::string("-pid_request")).c_str(), pid_cdev_request);
ATRACE_INT((atrace_prefix + std::string("-hardlimit_request")).c_str(),
hardlimit_cdev_request);
ATRACE_INT((atrace_prefix + std::string("-release_step")).c_str(), release_step);
ATRACE_INT((atrace_prefix + std::string("-cdev_floor")).c_str(), cdev_floor);
ATRACE_INT((atrace_prefix + std::string("-cdev_ceiling")).c_str(), cdev_ceiling);
auto request_state = std::max(pid_cdev_request, hardlimit_cdev_request);
if (release_step) {
if (release_step >= request_state) {
request_state = 0;
} else {
request_state = request_state - release_step;
}
// Only check the cdev_floor when release step is non zero
request_state = std::max(request_state, cdev_floor);
}
request_state = std::min(request_state, cdev_ceiling);
if (cdev_request_pair.second != request_state) {
if (updateCdevMaxRequestAndNotifyIfChange(cdev_name, cdev_request_pair.second,
request_state)) {
cooling_devices_to_update->emplace_back(cdev_name);
}
cdev_request_pair.second = request_state;
// Update sensor cdev request time in state
thermal_stats_helper->updateSensorCdevRequestStats(sensor_name, cdev_name,
cdev_request_pair.second);
}
}
}
bool ThermalThrottling::updateCdevMaxRequestAndNotifyIfChange(std::string_view cdev_name,
int cur_request, int new_request) {
std::unique_lock<std::shared_mutex> _lock(cdev_all_request_map_mutex_);
auto &request_set = cdev_all_request_map_.at(cdev_name.data());
int cur_max_request = (*request_set.begin());
// Remove old cdev request and add the new one.
request_set.erase(request_set.find(cur_request));
request_set.insert(new_request);
// Check if there is any change in aggregated max cdev request.
int new_max_request = (*request_set.begin());
LOG(VERBOSE) << "For cooling device [" << cdev_name.data()
<< "] cur_max_request is: " << cur_max_request
<< " new_max_request is: " << new_max_request;
return new_max_request != cur_max_request;
}
bool ThermalThrottling::getCdevMaxRequest(std::string_view cdev_name, int *max_state) {
std::shared_lock<std::shared_mutex> _lock(cdev_all_request_map_mutex_);
if (!cdev_all_request_map_.count(cdev_name.data())) {
LOG(ERROR) << "Cooling device [" << cdev_name.data()
<< "] not present in cooling device request map";
return false;
}
*max_state = *cdev_all_request_map_.at(cdev_name.data()).begin();
return true;
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,142 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <aidl/android/hardware/thermal/Temperature.h>
#include <queue>
#include <set>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "power_files.h"
#include "thermal_info.h"
#include "thermal_stats_helper.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
struct ThermalThrottlingStatus {
std::unordered_map<std::string, int> pid_power_budget_map;
std::unordered_map<std::string, int> pid_cdev_request_map;
std::unordered_map<std::string, int> hardlimit_cdev_request_map;
std::unordered_map<std::string, int> throttling_release_map;
std::unordered_map<std::string, int> cdev_status_map;
float prev_err;
float i_budget;
float prev_target;
float prev_power_budget;
float budget_transient;
int tran_cycle;
};
// Return the control temp target of PID algorithm
size_t getTargetStateOfPID(const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity);
// A helper class for conducting thermal throttling
class ThermalThrottling {
public:
ThermalThrottling() = default;
~ThermalThrottling() = default;
// Disallow copy and assign.
ThermalThrottling(const ThermalThrottling &) = delete;
void operator=(const ThermalThrottling &) = delete;
// Clear throttling data
void clearThrottlingData(std::string_view sensor_name, const SensorInfo &sensor_info);
// Register map for throttling algo
bool registerThermalThrottling(
std::string_view sensor_name, const std::shared_ptr<ThrottlingInfo> &throttling_info,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
// Register map for throttling release algo
bool registerThrottlingReleaseToWatch(std::string_view sensor_name, std::string_view cdev_name,
const BindedCdevInfo &binded_cdev_info);
// Get throttling status map
const std::unordered_map<std::string, ThermalThrottlingStatus> &GetThermalThrottlingStatusMap()
const {
std::shared_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
return thermal_throttling_status_map_;
}
// Update thermal throttling request for the specific sensor
void thermalThrottlingUpdate(
const Temperature &temp, const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
// Compute the throttling target from all the sensors' request
void computeCoolingDevicesRequest(std::string_view sensor_name, const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity,
std::vector<std::string> *cooling_devices_to_update,
ThermalStatsHelper *thermal_stats_helper);
// Get the aggregated (from all sensor) max request for a cooling device
bool getCdevMaxRequest(std::string_view cdev_name, int *max_state);
private:
// PID algo - get the total power budget
float updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info,
std::chrono::milliseconds time_elapsed_ms,
ThrottlingSeverity curr_severity);
// PID algo - return the power number from excluded power rail list
float computeExcludedPower(const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
std::string *log_buf, std::string_view sensor_name);
// PID algo - allocate the power to target CDEV according to the ODPM
bool allocatePowerToCdev(
const Temperature &temp, const SensorInfo &sensor_info,
const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
// PID algo - map the target throttling state according to the power budget
void updateCdevRequestByPower(
std::string sensor_name,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
// Hard limit algo - assign the throttling state according to the severity
void updateCdevRequestBySeverity(std::string_view sensor_name, const SensorInfo &sensor_info,
ThrottlingSeverity curr_severity);
// Throttling release algo - decide release step according to the predefined power threshold,
// return false if the throttling release is not registered in thermal config
bool throttlingReleaseUpdate(
std::string_view sensor_name,
const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
const std::unordered_map<std::string, PowerStatus> &power_status_map,
const ThrottlingSeverity severity, const SensorInfo &sensor_info);
// Update the cooling device request set for new request and notify the caller if there is
// change in max_request for the cooling device.
bool updateCdevMaxRequestAndNotifyIfChange(std::string_view cdev_name, int cur_request,
int new_request);
mutable std::shared_mutex thermal_throttling_status_map_mutex_;
// Thermal throttling status from each sensor
std::unordered_map<std::string, ThermalThrottlingStatus> thermal_throttling_status_map_;
std::shared_mutex cdev_all_request_map_mutex_;
// Set of all request for a cooling device from each sensor
std::unordered_map<std::string, std::multiset<int, std::greater<int>>> cdev_all_request_map_;
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,533 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
#include "thermal_watcher.h"
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <cutils/uevent.h>
#include <dirent.h>
#include <linux/netlink.h>
#include <linux/thermal.h>
#include <sys/inotify.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <utils/Trace.h>
#include <chrono>
#include <fstream>
#include "../thermal-helper.h"
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
namespace {
static int nlErrorHandle(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) {
int *ret = reinterpret_cast<int *>(arg);
*ret = err->error;
LOG(ERROR) << __func__ << "nl_groups: " << nla->nl_groups << ", nl_pid: " << nla->nl_pid;
return NL_STOP;
}
static int nlFinishHandle(struct nl_msg *msg, void *arg) {
int *ret = reinterpret_cast<int *>(arg);
*ret = 1;
struct nlmsghdr *nlh = nlmsg_hdr(msg);
LOG(VERBOSE) << __func__ << ": nlmsg type: " << nlh->nlmsg_type;
return NL_OK;
}
static int nlAckHandle(struct nl_msg *msg, void *arg) {
int *ret = reinterpret_cast<int *>(arg);
*ret = 1;
struct nlmsghdr *nlh = nlmsg_hdr(msg);
LOG(VERBOSE) << __func__ << ": nlmsg type: " << nlh->nlmsg_type;
return NL_OK;
}
static int nlSeqCheckHandle(struct nl_msg *msg, void *arg) {
int *ret = reinterpret_cast<int *>(arg);
*ret = 1;
struct nlmsghdr *nlh = nlmsg_hdr(msg);
LOG(VERBOSE) << __func__ << ": nlmsg type: " << nlh->nlmsg_type;
return NL_OK;
}
struct HandlerArgs {
const char *group;
int id;
};
static int nlSendMsg(struct nl_sock *sock, struct nl_msg *msg,
int (*rx_handler)(struct nl_msg *, void *), void *data) {
int err, done = 0;
std::unique_ptr<nl_cb, decltype(&nl_cb_put)> cb(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
err = nl_send_auto_complete(sock, msg);
if (err < 0)
return err;
err = 0;
nl_cb_err(cb.get(), NL_CB_CUSTOM, nlErrorHandle, &err);
nl_cb_set(cb.get(), NL_CB_FINISH, NL_CB_CUSTOM, nlFinishHandle, &done);
nl_cb_set(cb.get(), NL_CB_ACK, NL_CB_CUSTOM, nlAckHandle, &done);
if (rx_handler != NULL)
nl_cb_set(cb.get(), NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data);
while (err == 0 && done == 0) nl_recvmsgs(sock, cb.get());
return err;
}
static int nlFamilyHandle(struct nl_msg *msg, void *arg) {
struct HandlerArgs *grp = reinterpret_cast<struct HandlerArgs *>(arg);
struct nlattr *tb[CTRL_ATTR_MAX + 1];
struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
struct nlattr *mcgrp;
int rem_mcgrp;
nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
if (!tb[CTRL_ATTR_MCAST_GROUPS]) {
LOG(ERROR) << __func__ << "Multicast group not found";
return -1;
}
nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, reinterpret_cast<nlattr *>(nla_data(mcgrp)),
nla_len(mcgrp), NULL);
if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
continue;
if (strncmp(reinterpret_cast<char *>(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])),
grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])) != 0)
continue;
grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
break;
}
return 0;
}
static int nlGetMulticastId(struct nl_sock *sock, const char *family, const char *group) {
int err = 0, ctrlid;
struct HandlerArgs grp = {
.group = group,
.id = -ENOENT,
};
std::unique_ptr<nl_msg, decltype(&nlmsg_free)> msg(nlmsg_alloc(), nlmsg_free);
ctrlid = genl_ctrl_resolve(sock, "nlctrl");
genlmsg_put(msg.get(), 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
nla_put_string(msg.get(), CTRL_ATTR_FAMILY_NAME, family);
err = nlSendMsg(sock, msg.get(), nlFamilyHandle, &grp);
if (err)
return err;
err = grp.id;
LOG(INFO) << group << " multicast_id: " << grp.id;
return err;
}
static bool socketAddMembership(struct nl_sock *sock, const char *group) {
int mcid = nlGetMulticastId(sock, THERMAL_GENL_FAMILY_NAME, group);
if (mcid < 0) {
LOG(ERROR) << "Failed to get multicast id: " << group;
return false;
}
if (nl_socket_add_membership(sock, mcid)) {
LOG(ERROR) << "Failed to add netlink socket membership: " << group;
return false;
}
LOG(INFO) << "Added netlink socket membership: " << group;
return true;
}
static int handleEvent(struct nl_msg *n, void *arg) {
struct nlmsghdr *nlh = nlmsg_hdr(n);
struct genlmsghdr *glh = genlmsg_hdr(nlh);
struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
int *tz_id = reinterpret_cast<int *>(arg);
genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL);
if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_UP) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_UP";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
LOG(INFO) << "Thermal zone trip id: "
<< nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_DOWN) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_DOWN";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
LOG(INFO) << "Thermal zone trip id: "
<< nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_GOV_CHANGE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_GOV_CHANGE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_GOV_NAME])
LOG(INFO) << "Governor name: " << nla_get_string(attrs[THERMAL_GENL_ATTR_GOV_NAME]);
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_CREATE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_CREATE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_TZ_NAME])
LOG(INFO) << "Thermal zone name: " << nla_get_string(attrs[THERMAL_GENL_ATTR_TZ_NAME]);
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_DELETE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_DELETE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_DISABLE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_DISABLE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_ENABLE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_ENABLE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_CHANGE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_CHANGE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
LOG(INFO) << "Trip id:: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE])
LOG(INFO) << "Trip type: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP])
LOG(INFO) << "Trip temp: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST])
LOG(INFO) << "Trip hyst: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]);
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_ADD) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_ADD";
if (attrs[THERMAL_GENL_ATTR_TZ_ID])
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
LOG(INFO) << "Trip id:: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE])
LOG(INFO) << "Trip type: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP])
LOG(INFO) << "Trip temp: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]);
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST])
LOG(INFO) << "Trip hyst: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]);
}
if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_DELETE) {
LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_DELETE";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
LOG(INFO) << "Trip id:: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
}
if (glh->cmd == THERMAL_GENL_EVENT_CDEV_STATE_UPDATE) {
LOG(INFO) << "THERMAL_GENL_EVENT_CDEV_STATE_UPDATE";
if (attrs[THERMAL_GENL_ATTR_CDEV_ID])
LOG(INFO) << "Cooling device id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]);
if (attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE])
LOG(INFO) << "Cooling device current state: "
<< nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE]);
}
if (glh->cmd == THERMAL_GENL_EVENT_CDEV_ADD) {
LOG(INFO) << "THERMAL_GENL_EVENT_CDEV_ADD";
if (attrs[THERMAL_GENL_ATTR_CDEV_NAME])
LOG(INFO) << "Cooling device name: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_NAME]);
if (attrs[THERMAL_GENL_ATTR_CDEV_ID])
LOG(INFO) << "Cooling device id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]);
if (attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE])
LOG(INFO) << "Cooling device max state: "
<< nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE]);
}
if (glh->cmd == THERMAL_GENL_EVENT_CDEV_DELETE) {
LOG(INFO) << "THERMAL_GENL_EVENT_CDEV_DELETE";
if (attrs[THERMAL_GENL_ATTR_CDEV_ID])
LOG(INFO) << "Cooling device id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]);
}
if (glh->cmd == THERMAL_GENL_SAMPLING_TEMP) {
LOG(INFO) << "THERMAL_GENL_SAMPLING_TEMP";
if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
*tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
}
if (attrs[THERMAL_GENL_ATTR_TZ_TEMP])
LOG(INFO) << "Thermal zone temp: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]);
}
return 0;
}
} // namespace
void ThermalWatcher::registerFilesToWatch(const std::set<std::string> &sensors_to_watch) {
LOG(INFO) << "Uevent register file to watch...";
monitored_sensors_.insert(sensors_to_watch.begin(), sensors_to_watch.end());
uevent_fd_.reset((TEMP_FAILURE_RETRY(uevent_open_socket(64 * 1024, true))));
if (uevent_fd_.get() < 0) {
LOG(ERROR) << "failed to open uevent socket";
return;
}
fcntl(uevent_fd_, F_SETFL, O_NONBLOCK);
looper_->addFd(uevent_fd_.get(), 0, ::android::Looper::EVENT_INPUT, nullptr, nullptr);
sleep_ms_ = std::chrono::milliseconds(0);
last_update_time_ = boot_clock::now();
}
void ThermalWatcher::registerFilesToWatchNl(const std::set<std::string> &sensors_to_watch) {
LOG(INFO) << "Thermal genl register file to watch...";
monitored_sensors_.insert(sensors_to_watch.begin(), sensors_to_watch.end());
sk_thermal = nl_socket_alloc();
if (!sk_thermal) {
LOG(ERROR) << "nl_socket_alloc failed";
return;
}
if (genl_connect(sk_thermal)) {
LOG(ERROR) << "genl_connect failed: sk_thermal";
return;
}
thermal_genl_fd_.reset(nl_socket_get_fd(sk_thermal));
if (thermal_genl_fd_.get() < 0) {
LOG(ERROR) << "Failed to create thermal netlink socket";
return;
}
if (!socketAddMembership(sk_thermal, THERMAL_GENL_EVENT_GROUP_NAME)) {
return;
}
/*
* Currently, only the update_temperature() will send thermal genl samlping events
* from kernel. To avoid thermal-hal busy because samlping events are sent
* too frequently, ignore thermal genl samlping events until we figure out how to use it.
*
if (!socketAddMembership(sk_thermal, THERMAL_GENL_SAMPLING_GROUP_NAME)) {
return;
}
*/
fcntl(thermal_genl_fd_, F_SETFL, O_NONBLOCK);
looper_->addFd(thermal_genl_fd_.get(), 0, ::android::Looper::EVENT_INPUT, nullptr, nullptr);
sleep_ms_ = std::chrono::milliseconds(0);
last_update_time_ = boot_clock::now();
}
bool ThermalWatcher::startWatchingDeviceFiles() {
if (cb_) {
auto ret = this->run("FileWatcherThread", ::android::PRIORITY_HIGHEST);
if (ret != ::android::NO_ERROR) {
LOG(ERROR) << "ThermalWatcherThread start fail";
return false;
} else {
LOG(INFO) << "ThermalWatcherThread started";
return true;
}
}
return false;
}
void ThermalWatcher::parseUevent(std::set<std::string> *sensors_set) {
bool thermal_event = false;
constexpr int kUeventMsgLen = 2048;
char msg[kUeventMsgLen + 2];
char *cp;
while (true) {
int n = uevent_kernel_multicast_recv(uevent_fd_.get(), msg, kUeventMsgLen);
if (n <= 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
LOG(ERROR) << "Error reading from Uevent Fd";
}
break;
}
if (n >= kUeventMsgLen) {
LOG(ERROR) << "Uevent overflowed buffer, discarding";
continue;
}
msg[n] = '\0';
msg[n + 1] = '\0';
cp = msg;
while (*cp) {
std::string uevent = cp;
auto findSubSystemThermal = uevent.find("SUBSYSTEM=thermal");
if (!thermal_event) {
if (::android::base::StartsWith(uevent, "SUBSYSTEM=")) {
if (findSubSystemThermal != std::string::npos) {
thermal_event = true;
} else {
break;
}
}
} else {
auto start_pos = uevent.find("NAME=");
if (start_pos != std::string::npos) {
start_pos += 5;
std::string name = uevent.substr(start_pos);
if (monitored_sensors_.find(name) != monitored_sensors_.end()) {
sensors_set->insert(name);
}
break;
}
}
while (*cp++) {
}
}
}
}
// TODO(b/175367921): Consider for potentially adding more type of event in the function
// instead of just add the sensors to the list.
void ThermalWatcher::parseGenlink(std::set<std::string> *sensors_set) {
int err = 0, done = 0, tz_id = -1;
std::unique_ptr<nl_cb, decltype(&nl_cb_put)> cb(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
nl_cb_err(cb.get(), NL_CB_CUSTOM, nlErrorHandle, &err);
nl_cb_set(cb.get(), NL_CB_FINISH, NL_CB_CUSTOM, nlFinishHandle, &done);
nl_cb_set(cb.get(), NL_CB_ACK, NL_CB_CUSTOM, nlAckHandle, &done);
nl_cb_set(cb.get(), NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nlSeqCheckHandle, &done);
nl_cb_set(cb.get(), NL_CB_VALID, NL_CB_CUSTOM, handleEvent, &tz_id);
while (!done && !err) {
nl_recvmsgs(sk_thermal, cb.get());
if (tz_id < 0) {
break;
}
std::string name;
if (getThermalZoneTypeById(tz_id, &name) &&
monitored_sensors_.find(name) != monitored_sensors_.end()) {
sensors_set->insert(name);
}
}
}
void ThermalWatcher::wake() {
looper_->wake();
}
bool ThermalWatcher::threadLoop() {
LOG(VERBOSE) << "ThermalWatcher polling...";
int fd;
std::set<std::string> sensors;
auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(boot_clock::now() -
last_update_time_);
if (time_elapsed_ms < sleep_ms_ &&
looper_->pollOnce(sleep_ms_.count(), &fd, nullptr, nullptr) >= 0) {
ATRACE_NAME("ThermalWatcher::threadLoop - receive event");
if (fd != uevent_fd_.get() && fd != thermal_genl_fd_.get()) {
return true;
} else if (fd == thermal_genl_fd_.get()) {
parseGenlink(&sensors);
} else if (fd == uevent_fd_.get()) {
parseUevent(&sensors);
}
// Ignore cb_ if uevent is not from monitored sensors
if (sensors.size() == 0) {
return true;
}
}
sleep_ms_ = cb_(sensors);
last_update_time_ = boot_clock::now();
return true;
}
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl

@ -0,0 +1,114 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <android-base/chrono_utils.h>
#include <android-base/unique_fd.h>
#include <linux/genetlink.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
#include <chrono>
#include <condition_variable>
#include <future>
#include <list>
#include <mutex>
#include <set>
#include <string>
#include <thread>
#include <unordered_map>
#include <vector>
namespace aidl {
namespace android {
namespace hardware {
namespace thermal {
namespace implementation {
using ::android::base::boot_clock;
using ::android::base::unique_fd;
using WatcherCallback = std::function<std::chrono::milliseconds(const std::set<std::string> &name)>;
// A helper class for monitoring thermal files changes.
class ThermalWatcher : public ::android::Thread {
public:
explicit ThermalWatcher(const WatcherCallback &cb)
: Thread(false), cb_(cb), looper_(new ::android::Looper(true)) {}
~ThermalWatcher() = default;
// Disallow copy and assign.
ThermalWatcher(const ThermalWatcher &) = delete;
void operator=(const ThermalWatcher &) = delete;
// Start the thread and return true if it succeeds.
bool startWatchingDeviceFiles();
// Give the file watcher a list of files to start watching. This helper
// class will by default wait for modifications to the file with a looper.
// This should be called before starting watcher thread.
// For monitoring uevents.
void registerFilesToWatch(const std::set<std::string> &sensors_to_watch);
// For monitoring thermal genl events.
void registerFilesToWatchNl(const std::set<std::string> &sensors_to_watch);
// Wake up the looper thus the worker thread, immediately. This can be called
// in any thread.
void wake();
private:
// The work done by the watcher thread. This will use inotify to check for
// modifications to the files to watch. If any modification is seen this
// will callback the registered function with the new data read from the
// modified file.
bool threadLoop() override;
// Parse uevent message
void parseUevent(std::set<std::string> *sensor_name);
// Parse thermal netlink message
void parseGenlink(std::set<std::string> *sensor_name);
// Maps watcher filer descriptor to watched file path.
std::unordered_map<int, std::string> watch_to_file_path_map_;
// The callback function. Called whenever thermal uevent is seen.
// The function passed in should expect a string in the form (type).
// Where type is the name of the thermal zone that trigger a uevent notification.
// Callback will return thermal trigger status for next polling decision.
const WatcherCallback cb_;
::android::sp<::android::Looper> looper_;
// For uevent socket registration.
::android::base::unique_fd uevent_fd_;
// For thermal genl socket registration.
::android::base::unique_fd thermal_genl_fd_;
// Sensor list which monitor flag is enabled.
std::set<std::string> monitored_sensors_;
// Sleep interval voting result
std::chrono::milliseconds sleep_ms_;
// Timestamp for last thermal update
boot_clock::time_point last_update_time_;
// For thermal genl socket object.
struct nl_sock *sk_thermal;
};
} // namespace implementation
} // namespace thermal
} // namespace hardware
} // namespace android
} // namespace aidl
Loading…
Cancel
Save