--- /dev/null
+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",
+ ],
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+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
--- /dev/null
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.thermal</name>
+ <version>1</version>
+ <fqname>IThermal/default</fqname>
+ </hal>
+</manifest>
--- /dev/null
+#!/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
--- /dev/null
+#!/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
--- /dev/null
+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
--- /dev/null
+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
--- /dev/null
+/*
+ * 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
+}
--- /dev/null
+/*
+ * 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-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 <utils/Trace.h>
+
+#include <iterator>
+#include <set>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr std::string_view kThermalSensorsRoot("/sys/devices/virtual/thermal");
+constexpr std::string_view kSensorPrefix("thermal_zone");
+constexpr std::string_view kCoolingDevicePrefix("cooling_device");
+constexpr std::string_view kThermalNameFile("type");
+constexpr std::string_view kSensorPolicyFile("policy");
+constexpr std::string_view kSensorTempSuffix("temp");
+constexpr std::string_view kSensorTripPointTempZeroFile("trip_point_0_temp");
+constexpr std::string_view kSensorTripPointHystZeroFile("trip_point_0_hyst");
+constexpr std::string_view kUserSpaceSuffix("user_space");
+constexpr std::string_view kCoolingDeviceCurStateSuffix("cur_state");
+constexpr std::string_view kCoolingDeviceMaxStateSuffix("max_state");
+constexpr std::string_view kCoolingDeviceState2powerSuffix("state2power_table");
+constexpr std::string_view kConfigProperty("vendor.thermal.config");
+constexpr std::string_view kConfigDefaultFileName("thermal_info_config.json");
+constexpr std::string_view kThermalGenlProperty("persist.vendor.enable.thermal.genl");
+constexpr std::string_view kThermalDisabledProperty("vendor.disable.thermalhal.control");
+
+namespace {
+using ::android::base::StringPrintf;
+
+std::unordered_map<std::string, std::string> parseThermalPathMap(std::string_view prefix) {
+ std::unordered_map<std::string, std::string> path_map;
+ std::unique_ptr<DIR, int (*)(DIR *)> dir(opendir(kThermalSensorsRoot.data()), closedir);
+ if (!dir) {
+ return path_map;
+ }
+
+ // std::filesystem is not available for vendor yet
+ // see discussion: aosp/894015
+ while (struct dirent *dp = readdir(dir.get())) {
+ if (dp->d_type != DT_DIR) {
+ continue;
+ }
+
+ if (!::android::base::StartsWith(dp->d_name, prefix.data())) {
+ continue;
+ }
+
+ std::string path = ::android::base::StringPrintf("%s/%s/%s", kThermalSensorsRoot.data(),
+ dp->d_name, kThermalNameFile.data());
+ std::string name;
+ if (!::android::base::ReadFileToString(path, &name)) {
+ PLOG(ERROR) << "Failed to read from " << path;
+ continue;
+ }
+
+ path_map.emplace(
+ ::android::base::Trim(name),
+ ::android::base::StringPrintf("%s/%s", kThermalSensorsRoot.data(), dp->d_name));
+ }
+
+ return path_map;
+}
+
+} // namespace
+
+/*
+ * Populate the sensor_name_to_file_map_ map by walking through the file tree,
+ * reading the type file and assigning the temp file path to the map. If we do
+ * not succeed, abort.
+ */
+ThermalHelper::ThermalHelper(const NotificationCallback &cb)
+ : thermal_watcher_(new ThermalWatcher(
+ std::bind(&ThermalHelper::thermalWatcherCallbackFunc, this, std::placeholders::_1))),
+ cb_(cb) {
+ const std::string config_path =
+ "/vendor/etc/" +
+ ::android::base::GetProperty(kConfigProperty.data(), kConfigDefaultFileName.data());
+ bool thermal_throttling_disabled =
+ ::android::base::GetBoolProperty(kThermalDisabledProperty.data(), false);
+ bool ret = true;
+ Json::Value config;
+ if (!ParseThermalConfig(config_path, &config)) {
+ LOG(ERROR) << "Failed to read JSON config";
+ ret = false;
+ }
+
+ if (!ParseCoolingDevice(config, &cooling_device_info_map_)) {
+ LOG(ERROR) << "Failed to parse cooling device info config";
+ ret = false;
+ }
+
+ if (!ParseSensorInfo(config, &sensor_info_map_)) {
+ LOG(ERROR) << "Failed to parse sensor info config";
+ ret = false;
+ }
+
+ auto tz_map = parseThermalPathMap(kSensorPrefix.data());
+ if (!initializeSensorMap(tz_map)) {
+ LOG(ERROR) << "Failed to initialize sensor map";
+ ret = false;
+ }
+
+ auto cdev_map = parseThermalPathMap(kCoolingDevicePrefix.data());
+ if (!initializeCoolingDevices(cdev_map)) {
+ LOG(ERROR) << "Failed to initialize cooling device map";
+ ret = false;
+ }
+
+ if (!power_files_.registerPowerRailsToWatch(config)) {
+ LOG(ERROR) << "Failed to register power rails";
+ ret = false;
+ }
+
+ if (!thermal_stats_helper_.initializeStats(config, sensor_info_map_,
+ cooling_device_info_map_)) {
+ LOG(FATAL) << "Failed to initialize thermal stats";
+ }
+
+ for (auto const &name_status_pair : sensor_info_map_) {
+ sensor_status_map_[name_status_pair.first] = {
+ .severity = ThrottlingSeverity::NONE,
+ .prev_hot_severity = ThrottlingSeverity::NONE,
+ .prev_cold_severity = ThrottlingSeverity::NONE,
+ .prev_hint_severity = ThrottlingSeverity::NONE,
+ .last_update_time = boot_clock::time_point::min(),
+ .thermal_cached = {NAN, boot_clock::time_point::min()},
+ .emul_setting = nullptr,
+ };
+
+ if (name_status_pair.second.throttling_info != nullptr) {
+ if (!thermal_throttling_.registerThermalThrottling(
+ name_status_pair.first, name_status_pair.second.throttling_info,
+ cooling_device_info_map_)) {
+ LOG(ERROR) << name_status_pair.first << " failed to register thermal throttling";
+ ret = false;
+ break;
+ }
+
+ // Update cooling device max state
+ for (auto &binded_cdev_info_pair :
+ name_status_pair.second.throttling_info->binded_cdev_info_map) {
+ const auto &cdev_info = cooling_device_info_map_.at(binded_cdev_info_pair.first);
+
+ for (auto &cdev_ceiling : binded_cdev_info_pair.second.cdev_ceiling) {
+ if (cdev_ceiling > cdev_info.max_state) {
+ if (cdev_ceiling != std::numeric_limits<int>::max()) {
+ LOG(WARNING) << "Sensor " << name_status_pair.first << "'s "
+ << binded_cdev_info_pair.first
+ << " cdev_ceiling:" << cdev_ceiling
+ << " is higher than max state:" << cdev_info.max_state;
+ }
+ cdev_ceiling = cdev_info.max_state;
+ }
+ }
+ }
+ }
+ // Check the virtual sensor settings are valid
+ if (name_status_pair.second.virtual_sensor_info != nullptr) {
+ // Check if sub sensor setting is valid
+ for (size_t i = 0;
+ i < name_status_pair.second.virtual_sensor_info->linked_sensors.size(); i++) {
+ if (!isSubSensorValid(
+ name_status_pair.second.virtual_sensor_info->linked_sensors[i],
+ name_status_pair.second.virtual_sensor_info->linked_sensors_type[i])) {
+ LOG(ERROR) << name_status_pair.first << "'s link sensor "
+ << name_status_pair.second.virtual_sensor_info->linked_sensors[i]
+ << " is invalid";
+ ret = false;
+ break;
+ }
+ }
+
+ // Check if the trigger sensor is valid
+ if (!name_status_pair.second.virtual_sensor_info->trigger_sensors.empty() &&
+ name_status_pair.second.is_watch) {
+ for (size_t i = 0;
+ i < name_status_pair.second.virtual_sensor_info->trigger_sensors.size(); i++) {
+ if (sensor_info_map_.count(
+ name_status_pair.second.virtual_sensor_info->trigger_sensors[i])) {
+ sensor_info_map_[name_status_pair.second.virtual_sensor_info
+ ->trigger_sensors[i]]
+ .is_watch = true;
+ } else {
+ LOG(ERROR)
+ << name_status_pair.first << "'s trigger sensor: "
+ << name_status_pair.second.virtual_sensor_info->trigger_sensors[i]
+ << " is invalid";
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (!connectToPowerHal()) {
+ LOG(ERROR) << "Fail to connect to Power Hal";
+ } else {
+ updateSupportedPowerHints();
+ }
+
+ if (thermal_throttling_disabled) {
+ if (ret) {
+ clearAllThrottling();
+ is_initialized_ = ret;
+ return;
+ } else {
+ sensor_info_map_.clear();
+ cooling_device_info_map_.clear();
+ return;
+ }
+ } else if (!ret) {
+ LOG(FATAL) << "ThermalHAL could not be initialized properly.";
+ }
+ is_initialized_ = ret;
+
+ const bool thermal_genl_enabled =
+ ::android::base::GetBoolProperty(kThermalGenlProperty.data(), false);
+
+ std::set<std::string> monitored_sensors;
+ initializeTrip(tz_map, &monitored_sensors, thermal_genl_enabled);
+
+ if (thermal_genl_enabled) {
+ thermal_watcher_->registerFilesToWatchNl(monitored_sensors);
+ } else {
+ thermal_watcher_->registerFilesToWatch(monitored_sensors);
+ }
+
+ // Need start watching after status map initialized
+ is_initialized_ = thermal_watcher_->startWatchingDeviceFiles();
+ if (!is_initialized_) {
+ LOG(FATAL) << "ThermalHAL could not start watching thread properly.";
+ }
+
+ if (!connectToPowerHal()) {
+ LOG(ERROR) << "Fail to connect to Power Hal";
+ } else {
+ updateSupportedPowerHints();
+ }
+}
+
+bool getThermalZoneTypeById(int tz_id, std::string *type) {
+ std::string tz_type;
+ std::string path =
+ ::android::base::StringPrintf("%s/%s%d/%s", kThermalSensorsRoot.data(),
+ kSensorPrefix.data(), tz_id, kThermalNameFile.data());
+ LOG(INFO) << "TZ Path: " << path;
+ if (!::android::base::ReadFileToString(path, &tz_type)) {
+ LOG(ERROR) << "Failed to read sensor: " << tz_type;
+ return false;
+ }
+
+ // Strip the newline.
+ *type = ::android::base::Trim(tz_type);
+ LOG(INFO) << "TZ type: " << *type;
+ return true;
+}
+
+bool ThermalHelper::emulTemp(std::string_view target_sensor, const float value) {
+ LOG(INFO) << "Set " << target_sensor.data() << " emul_temp "
+ << "to " << value;
+
+ std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ // Check the target sensor is valid
+ if (!sensor_status_map_.count(target_sensor.data())) {
+ LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
+ return false;
+ }
+
+ sensor_status_map_.at(target_sensor.data())
+ .emul_setting.reset(new EmulSetting{value, -1, true});
+
+ thermal_watcher_->wake();
+ return true;
+}
+
+bool ThermalHelper::emulSeverity(std::string_view target_sensor, const int severity) {
+ LOG(INFO) << "Set " << target_sensor.data() << " emul_severity "
+ << "to " << severity;
+
+ std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ // Check the target sensor is valid
+ if (!sensor_status_map_.count(target_sensor.data())) {
+ LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
+ return false;
+ }
+ // Check the emul severity is valid
+ if (severity > static_cast<int>(kThrottlingSeverityCount)) {
+ LOG(ERROR) << "Invalid emul severity value " << severity;
+ return false;
+ }
+
+ sensor_status_map_.at(target_sensor.data())
+ .emul_setting.reset(new EmulSetting{NAN, severity, true});
+
+ thermal_watcher_->wake();
+ return true;
+}
+
+bool ThermalHelper::emulClear(std::string_view target_sensor) {
+ LOG(INFO) << "Clear " << target_sensor.data() << " emulation settings";
+
+ std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ if (target_sensor == "all") {
+ for (auto &sensor_status : sensor_status_map_) {
+ if (sensor_status.second.emul_setting != nullptr) {
+ sensor_status.second.emul_setting.reset(new EmulSetting{NAN, -1, true});
+ }
+ }
+ } else if (sensor_status_map_.count(target_sensor.data()) &&
+ sensor_status_map_.at(target_sensor.data()).emul_setting != nullptr) {
+ sensor_status_map_.at(target_sensor.data())
+ .emul_setting.reset(new EmulSetting{NAN, -1, true});
+ } else {
+ LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
+ return false;
+ }
+ return true;
+}
+
+bool ThermalHelper::readCoolingDevice(std::string_view cooling_device, CoolingDevice *out) const {
+ // Read the file. If the file can't be read temp will be empty string.
+ std::string data;
+
+ if (!cooling_devices_.readThermalFile(cooling_device, &data)) {
+ LOG(ERROR) << "readCoolingDevice: failed to read cooling_device: " << cooling_device;
+ return false;
+ }
+
+ const CdevInfo &cdev_info = cooling_device_info_map_.at(cooling_device.data());
+ const CoolingType &type = cdev_info.type;
+
+ out->type = type;
+ out->name = cooling_device.data();
+ out->value = std::stoi(data);
+
+ return true;
+}
+
+bool ThermalHelper::readTemperature(
+ std::string_view sensor_name, Temperature *out,
+ std::pair<ThrottlingSeverity, ThrottlingSeverity> *throttling_status,
+ const bool force_no_cache) {
+ // Return fail if the thermal sensor cannot be read.
+ float temp;
+ std::map<std::string, float> sensor_log_map;
+ auto &sensor_status = sensor_status_map_.at(sensor_name.data());
+
+ if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log_map)) {
+ LOG(ERROR) << "readTemperature: failed to read sensor: " << sensor_name;
+ return false;
+ }
+
+ const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+ out->type = sensor_info.type;
+ out->name = sensor_name.data();
+ out->value = temp * sensor_info.multiplier;
+
+ std::pair<ThrottlingSeverity, ThrottlingSeverity> status =
+ std::make_pair(ThrottlingSeverity::NONE, ThrottlingSeverity::NONE);
+ // Only update status if the thermal sensor is being monitored
+ if (sensor_info.is_watch) {
+ ThrottlingSeverity prev_hot_severity, prev_cold_severity;
+ {
+ // reader lock, readTemperature will be called in Binder call and the watcher thread.
+ std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ prev_hot_severity = sensor_status.prev_hot_severity;
+ prev_cold_severity = sensor_status.prev_cold_severity;
+ }
+ status = getSeverityFromThresholds(sensor_info.hot_thresholds, sensor_info.cold_thresholds,
+ sensor_info.hot_hysteresis, sensor_info.cold_hysteresis,
+ prev_hot_severity, prev_cold_severity, out->value);
+ }
+
+ if (throttling_status) {
+ *throttling_status = status;
+ }
+
+ if (sensor_status.emul_setting != nullptr && sensor_status.emul_setting->emul_severity >= 0) {
+ std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ out->throttlingStatus =
+ static_cast<ThrottlingSeverity>(sensor_status.emul_setting->emul_severity);
+ } else {
+ out->throttlingStatus =
+ static_cast<size_t>(status.first) > static_cast<size_t>(status.second)
+ ? status.first
+ : status.second;
+ }
+ if (sensor_info.is_watch) {
+ std::ostringstream sensor_log;
+ for (const auto &sensor_log_pair : sensor_log_map) {
+ sensor_log << sensor_log_pair.first << ":" << sensor_log_pair.second << " ";
+ }
+ // Update sensor temperature time in state
+ thermal_stats_helper_.updateSensorTempStatsBySeverity(sensor_name, out->throttlingStatus);
+ LOG(INFO) << sensor_name.data() << ":" << out->value << " raw data: " << sensor_log.str();
+ }
+
+ return true;
+}
+
+bool ThermalHelper::readTemperatureThreshold(std::string_view sensor_name,
+ TemperatureThreshold *out) const {
+ // Read the file. If the file can't be read temp will be empty string.
+ std::string temp;
+ std::string path;
+
+ if (!sensor_info_map_.count(sensor_name.data())) {
+ LOG(ERROR) << __func__ << ": sensor not found: " << sensor_name;
+ return false;
+ }
+
+ const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+
+ out->type = sensor_info.type;
+ out->name = sensor_name.data();
+ out->hotThrottlingThresholds =
+ std::vector(sensor_info.hot_thresholds.begin(), sensor_info.hot_thresholds.end());
+ out->coldThrottlingThresholds =
+ std::vector(sensor_info.cold_thresholds.begin(), sensor_info.cold_thresholds.end());
+ return true;
+}
+
+void ThermalHelper::updateCoolingDevices(const std::vector<std::string> &updated_cdev) {
+ int max_state;
+
+ for (const auto &target_cdev : updated_cdev) {
+ if (thermal_throttling_.getCdevMaxRequest(target_cdev, &max_state)) {
+ if (cooling_devices_.writeCdevFile(target_cdev, std::to_string(max_state))) {
+ ATRACE_INT(target_cdev.c_str(), max_state);
+ LOG(INFO) << "Successfully update cdev " << target_cdev << " sysfs to "
+ << max_state;
+ } else {
+ LOG(ERROR) << "Failed to update cdev " << target_cdev << " sysfs to " << max_state;
+ }
+ }
+ }
+}
+
+std::pair<ThrottlingSeverity, ThrottlingSeverity> ThermalHelper::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 {
+ ThrottlingSeverity ret_hot = ThrottlingSeverity::NONE;
+ ThrottlingSeverity ret_hot_hysteresis = ThrottlingSeverity::NONE;
+ ThrottlingSeverity ret_cold = ThrottlingSeverity::NONE;
+ ThrottlingSeverity ret_cold_hysteresis = ThrottlingSeverity::NONE;
+
+ // Here we want to control the iteration from high to low, and ::ndk::enum_range doesn't support
+ // a reverse iterator yet.
+ for (size_t i = static_cast<size_t>(ThrottlingSeverity::SHUTDOWN);
+ i > static_cast<size_t>(ThrottlingSeverity::NONE); --i) {
+ if (!std::isnan(hot_thresholds[i]) && hot_thresholds[i] <= value &&
+ ret_hot == ThrottlingSeverity::NONE) {
+ ret_hot = static_cast<ThrottlingSeverity>(i);
+ }
+ if (!std::isnan(hot_thresholds[i]) && (hot_thresholds[i] - hot_hysteresis[i]) < value &&
+ ret_hot_hysteresis == ThrottlingSeverity::NONE) {
+ ret_hot_hysteresis = static_cast<ThrottlingSeverity>(i);
+ }
+ if (!std::isnan(cold_thresholds[i]) && cold_thresholds[i] >= value &&
+ ret_cold == ThrottlingSeverity::NONE) {
+ ret_cold = static_cast<ThrottlingSeverity>(i);
+ }
+ if (!std::isnan(cold_thresholds[i]) && (cold_thresholds[i] + cold_hysteresis[i]) > value &&
+ ret_cold_hysteresis == ThrottlingSeverity::NONE) {
+ ret_cold_hysteresis = static_cast<ThrottlingSeverity>(i);
+ }
+ }
+ if (static_cast<size_t>(ret_hot) < static_cast<size_t>(prev_hot_severity)) {
+ ret_hot = ret_hot_hysteresis;
+ }
+ if (static_cast<size_t>(ret_cold) < static_cast<size_t>(prev_cold_severity)) {
+ ret_cold = ret_cold_hysteresis;
+ }
+
+ return std::make_pair(ret_hot, ret_cold);
+}
+
+bool ThermalHelper::isSubSensorValid(std::string_view sensor_data,
+ const SensorFusionType sensor_fusion_type) {
+ switch (sensor_fusion_type) {
+ case SensorFusionType::SENSOR:
+ if (!sensor_info_map_.count(sensor_data.data())) {
+ LOG(ERROR) << "Cannot find " << sensor_data.data() << " from sensor info map";
+ return false;
+ }
+ break;
+ case SensorFusionType::ODPM:
+ if (!GetPowerStatusMap().count(sensor_data.data())) {
+ LOG(ERROR) << "Cannot find " << sensor_data.data() << " from power status map";
+ return false;
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+void ThermalHelper::clearAllThrottling(void) {
+ // Clear the CDEV request
+ for (const auto &cdev_info_pair : cooling_device_info_map_) {
+ cooling_devices_.writeCdevFile(cdev_info_pair.first, "0");
+ }
+
+ for (auto &sensor_info_pair : sensor_info_map_) {
+ sensor_info_pair.second.is_watch = false;
+ sensor_info_pair.second.throttling_info.reset();
+ sensor_info_pair.second.hot_thresholds.fill(NAN);
+ sensor_info_pair.second.cold_thresholds.fill(NAN);
+ Temperature temp = {
+ .type = sensor_info_pair.second.type,
+ .name = sensor_info_pair.first,
+ .value = NAN,
+ .throttlingStatus = ThrottlingSeverity::NONE,
+ };
+ // Send callbacks with NONE severity
+ if (sensor_info_pair.second.send_cb && cb_) {
+ cb_(temp);
+ }
+ // Disable thermal power hints
+ if (sensor_info_pair.second.send_powerhint) {
+ for (const auto &severity : ::ndk::enum_range<ThrottlingSeverity>()) {
+ power_hal_service_.setMode(sensor_info_pair.first, severity, false);
+ }
+ }
+ }
+}
+
+bool ThermalHelper::initializeSensorMap(
+ const std::unordered_map<std::string, std::string> &path_map) {
+ for (const auto &sensor_info_pair : sensor_info_map_) {
+ std::string_view sensor_name = sensor_info_pair.first;
+ if (sensor_info_pair.second.virtual_sensor_info != nullptr) {
+ continue;
+ }
+ if (!path_map.count(sensor_name.data())) {
+ LOG(ERROR) << "Could not find " << sensor_name << " in sysfs";
+ return false;
+ }
+
+ std::string path;
+ if (sensor_info_pair.second.temp_path.empty()) {
+ path = ::android::base::StringPrintf("%s/%s", path_map.at(sensor_name.data()).c_str(),
+ kSensorTempSuffix.data());
+ } else {
+ path = sensor_info_pair.second.temp_path;
+ }
+
+ if (!thermal_sensors_.addThermalFile(sensor_name, path)) {
+ LOG(ERROR) << "Could not add " << sensor_name << "to sensors map";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ThermalHelper::initializeCoolingDevices(
+ const std::unordered_map<std::string, std::string> &path_map) {
+ for (auto &cooling_device_info_pair : cooling_device_info_map_) {
+ std::string cooling_device_name = cooling_device_info_pair.first;
+ if (!path_map.count(cooling_device_name)) {
+ LOG(ERROR) << "Could not find " << cooling_device_name << " in sysfs";
+ return false;
+ }
+ // Add cooling device path for thermalHAL to get current state
+ std::string_view path = path_map.at(cooling_device_name);
+ std::string read_path;
+ if (!cooling_device_info_pair.second.read_path.empty()) {
+ read_path = cooling_device_info_pair.second.read_path.data();
+ } else {
+ read_path = ::android::base::StringPrintf("%s/%s", path.data(),
+ kCoolingDeviceCurStateSuffix.data());
+ }
+ if (!cooling_devices_.addThermalFile(cooling_device_name, read_path)) {
+ LOG(ERROR) << "Could not add " << cooling_device_name
+ << " read path to cooling device map";
+ return false;
+ }
+
+ std::string state2power_path = ::android::base::StringPrintf(
+ "%s/%s", path.data(), kCoolingDeviceState2powerSuffix.data());
+ std::string state2power_str;
+ if (::android::base::ReadFileToString(state2power_path, &state2power_str)) {
+ LOG(INFO) << "Cooling device " << cooling_device_info_pair.first
+ << " use state2power read from sysfs";
+ cooling_device_info_pair.second.state2power.clear();
+
+ std::stringstream power(state2power_str);
+ unsigned int power_number;
+ int i = 0;
+ while (power >> power_number) {
+ cooling_device_info_pair.second.state2power.push_back(
+ static_cast<float>(power_number));
+ LOG(INFO) << "Cooling device " << cooling_device_info_pair.first << " state:" << i
+ << " power: " << power_number;
+ i++;
+ }
+ }
+
+ // Get max cooling device request state
+ std::string max_state;
+ std::string max_state_path = ::android::base::StringPrintf(
+ "%s/%s", path.data(), kCoolingDeviceMaxStateSuffix.data());
+ if (!::android::base::ReadFileToString(max_state_path, &max_state)) {
+ LOG(ERROR) << cooling_device_info_pair.first
+ << " could not open max state file:" << max_state_path;
+ cooling_device_info_pair.second.max_state = std::numeric_limits<int>::max();
+ } else {
+ cooling_device_info_pair.second.max_state = std::stoi(::android::base::Trim(max_state));
+ LOG(INFO) << "Cooling device " << cooling_device_info_pair.first
+ << " max state: " << cooling_device_info_pair.second.max_state
+ << " state2power number: "
+ << cooling_device_info_pair.second.state2power.size();
+ if (cooling_device_info_pair.second.state2power.size() > 0 &&
+ static_cast<int>(cooling_device_info_pair.second.state2power.size()) !=
+ (cooling_device_info_pair.second.max_state + 1)) {
+ LOG(ERROR) << "Invalid state2power number: "
+ << cooling_device_info_pair.second.state2power.size()
+ << ", number should be " << cooling_device_info_pair.second.max_state + 1
+ << " (max_state + 1)";
+ return false;
+ }
+ }
+
+ // Add cooling device path for thermalHAL to request state
+ cooling_device_name =
+ ::android::base::StringPrintf("%s_%s", cooling_device_name.c_str(), "w");
+ std::string write_path;
+ if (!cooling_device_info_pair.second.write_path.empty()) {
+ write_path = cooling_device_info_pair.second.write_path.data();
+ } else {
+ write_path = ::android::base::StringPrintf("%s/%s", path.data(),
+ kCoolingDeviceCurStateSuffix.data());
+ }
+
+ if (!cooling_devices_.addThermalFile(cooling_device_name, write_path)) {
+ LOG(ERROR) << "Could not add " << cooling_device_name
+ << " write path to cooling device map";
+ return false;
+ }
+ }
+ return true;
+}
+
+void ThermalHelper::setMinTimeout(SensorInfo *sensor_info) {
+ sensor_info->polling_delay = kMinPollIntervalMs;
+ sensor_info->passive_delay = kMinPollIntervalMs;
+}
+
+void ThermalHelper::initializeTrip(const std::unordered_map<std::string, std::string> &path_map,
+ std::set<std::string> *monitored_sensors,
+ bool thermal_genl_enabled) {
+ for (auto &sensor_info : sensor_info_map_) {
+ if (!sensor_info.second.is_watch || (sensor_info.second.virtual_sensor_info != nullptr)) {
+ continue;
+ }
+
+ bool trip_update = false;
+ std::string_view sensor_name = sensor_info.first;
+ std::string_view tz_path = path_map.at(sensor_name.data());
+ std::string tz_policy;
+ std::string path =
+ ::android::base::StringPrintf("%s/%s", (tz_path.data()), kSensorPolicyFile.data());
+
+ if (thermal_genl_enabled) {
+ trip_update = true;
+ } else {
+ // Check if thermal zone support uevent notify
+ if (!::android::base::ReadFileToString(path, &tz_policy)) {
+ LOG(ERROR) << sensor_name << " could not open tz policy file:" << path;
+ } else {
+ tz_policy = ::android::base::Trim(tz_policy);
+ if (tz_policy != kUserSpaceSuffix) {
+ LOG(ERROR) << sensor_name << " does not support uevent notify";
+ } else {
+ trip_update = true;
+ }
+ }
+ }
+ if (trip_update) {
+ // Update thermal zone trip point
+ for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+ if (!std::isnan(sensor_info.second.hot_thresholds[i]) &&
+ !std::isnan(sensor_info.second.hot_hysteresis[i])) {
+ // Update trip_point_0_temp threshold
+ std::string threshold = std::to_string(static_cast<int>(
+ sensor_info.second.hot_thresholds[i] / sensor_info.second.multiplier));
+ path = ::android::base::StringPrintf("%s/%s", (tz_path.data()),
+ kSensorTripPointTempZeroFile.data());
+ if (!::android::base::WriteStringToFile(threshold, path)) {
+ LOG(ERROR) << "fail to update " << sensor_name << " trip point: " << path
+ << " to " << threshold;
+ trip_update = false;
+ break;
+ }
+ // Update trip_point_0_hyst threshold
+ threshold = std::to_string(static_cast<int>(
+ sensor_info.second.hot_hysteresis[i] / sensor_info.second.multiplier));
+ path = ::android::base::StringPrintf("%s/%s", (tz_path.data()),
+ kSensorTripPointHystZeroFile.data());
+ if (!::android::base::WriteStringToFile(threshold, path)) {
+ LOG(ERROR) << "fail to update " << sensor_name << "trip hyst" << threshold
+ << path;
+ trip_update = false;
+ break;
+ }
+ break;
+ } else if (i == kThrottlingSeverityCount - 1) {
+ LOG(ERROR) << sensor_name << ":all thresholds are NAN";
+ trip_update = false;
+ break;
+ }
+ }
+ monitored_sensors->insert(sensor_info.first);
+ }
+
+ if (!trip_update) {
+ LOG(INFO) << "config Sensor: " << sensor_info.first
+ << " to default polling interval: " << kMinPollIntervalMs.count();
+ setMinTimeout(&sensor_info.second);
+ }
+ }
+}
+
+bool ThermalHelper::fillCurrentTemperatures(bool filterType, bool filterCallback,
+ TemperatureType type,
+ std::vector<Temperature> *temperatures) {
+ std::vector<Temperature> ret;
+ for (const auto &name_info_pair : sensor_info_map_) {
+ Temperature temp;
+ if (name_info_pair.second.is_hidden) {
+ continue;
+ }
+ if (filterType && name_info_pair.second.type != type) {
+ continue;
+ }
+ if (filterCallback && !name_info_pair.second.send_cb) {
+ continue;
+ }
+ if (readTemperature(name_info_pair.first, &temp, nullptr, false)) {
+ ret.emplace_back(std::move(temp));
+ } else {
+ LOG(ERROR) << __func__
+ << ": error reading temperature for sensor: " << name_info_pair.first;
+ }
+ }
+ *temperatures = ret;
+ return ret.size() > 0;
+}
+
+bool ThermalHelper::fillTemperatureThresholds(bool filterType, TemperatureType type,
+ std::vector<TemperatureThreshold> *thresholds) const {
+ std::vector<TemperatureThreshold> ret;
+ for (const auto &name_info_pair : sensor_info_map_) {
+ TemperatureThreshold temp;
+ if (name_info_pair.second.is_hidden) {
+ continue;
+ }
+ if (filterType && name_info_pair.second.type != type) {
+ continue;
+ }
+ if (readTemperatureThreshold(name_info_pair.first, &temp)) {
+ ret.emplace_back(std::move(temp));
+ } else {
+ LOG(ERROR) << __func__ << ": error reading temperature threshold for sensor: "
+ << name_info_pair.first;
+ return false;
+ }
+ }
+ *thresholds = ret;
+ return ret.size() > 0;
+}
+
+bool ThermalHelper::fillCurrentCoolingDevices(bool filterType, CoolingType type,
+ std::vector<CoolingDevice> *cooling_devices) const {
+ std::vector<CoolingDevice> ret;
+ for (const auto &name_info_pair : cooling_device_info_map_) {
+ CoolingDevice value;
+ if (filterType && name_info_pair.second.type != type) {
+ continue;
+ }
+ if (readCoolingDevice(name_info_pair.first, &value)) {
+ ret.emplace_back(std::move(value));
+ } else {
+ LOG(ERROR) << __func__ << ": error reading cooling device: " << name_info_pair.first;
+ return false;
+ }
+ }
+ *cooling_devices = ret;
+ return ret.size() > 0;
+}
+
+bool ThermalHelper::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) {
+ switch (type) {
+ case SensorFusionType::SENSOR:
+ if (!readThermalSensor(sensor_data.data(), reading_value, force_no_cache,
+ sensor_log_map)) {
+ LOG(ERROR) << "Failed to get " << sensor_data.data() << " data";
+ return false;
+ }
+ break;
+ case SensorFusionType::ODPM:
+ *reading_value = GetPowerStatusMap().at(sensor_data.data()).last_updated_avg_power;
+ if (std::isnan(*reading_value)) {
+ LOG(INFO) << "Power data " << sensor_data.data() << " is under collecting";
+ return false;
+ }
+ (*sensor_log_map)[sensor_data.data()] = *reading_value;
+ break;
+ default:
+ break;
+ }
+ return true;
+}
+
+bool ThermalHelper::readThermalSensor(std::string_view sensor_name, float *temp,
+ const bool force_no_cache,
+ std::map<std::string, float> *sensor_log_map) {
+ float temp_val = 0.0;
+ std::string file_reading;
+ boot_clock::time_point now = boot_clock::now();
+
+ ATRACE_NAME(StringPrintf("ThermalHelper::readThermalSensor - %s", sensor_name.data()).c_str());
+ if (!(sensor_info_map_.count(sensor_name.data()) &&
+ sensor_status_map_.count(sensor_name.data()))) {
+ return false;
+ }
+
+ const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+ auto &sensor_status = sensor_status_map_.at(sensor_name.data());
+
+ {
+ std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ if (sensor_status.emul_setting != nullptr &&
+ !isnan(sensor_status.emul_setting->emul_temp)) {
+ *temp = sensor_status.emul_setting->emul_temp;
+ return true;
+ }
+ }
+
+ // Check if thermal data need to be read from cache
+ if (!force_no_cache &&
+ (sensor_status.thermal_cached.timestamp != boot_clock::time_point::min()) &&
+ (std::chrono::duration_cast<std::chrono::milliseconds>(
+ now - sensor_status.thermal_cached.timestamp) < sensor_info.time_resolution) &&
+ !isnan(sensor_status.thermal_cached.temp)) {
+ *temp = sensor_status.thermal_cached.temp;
+ (*sensor_log_map)[sensor_name.data()] = *temp;
+ ATRACE_INT((sensor_name.data() + std::string("-cached")).c_str(), static_cast<int>(*temp));
+ return true;
+ }
+
+ // Reading thermal sensor according to it's composition
+ if (sensor_info.virtual_sensor_info == nullptr) {
+ if (!thermal_sensors_.readThermalFile(sensor_name.data(), &file_reading)) {
+ return false;
+ }
+
+ if (file_reading.empty()) {
+ LOG(ERROR) << "failed to read sensor: " << sensor_name;
+ return false;
+ }
+ *temp = std::stof(::android::base::Trim(file_reading));
+ } else {
+ for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) {
+ float sensor_reading = 0.0;
+ // Get the sensor reading data
+ if (!readDataByType(sensor_info.virtual_sensor_info->linked_sensors[i], &sensor_reading,
+ sensor_info.virtual_sensor_info->linked_sensors_type[i],
+ force_no_cache, sensor_log_map)) {
+ LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s linked sensor "
+ << sensor_info.virtual_sensor_info->linked_sensors[i];
+ }
+ if (std::isnan(sensor_info.virtual_sensor_info->coefficients[i])) {
+ return false;
+ }
+ float coefficient = sensor_info.virtual_sensor_info->coefficients[i];
+ switch (sensor_info.virtual_sensor_info->formula) {
+ case FormulaOption::COUNT_THRESHOLD:
+ if ((coefficient < 0 && sensor_reading < -coefficient) ||
+ (coefficient >= 0 && sensor_reading >= coefficient))
+ temp_val += 1;
+ break;
+ case FormulaOption::WEIGHTED_AVG:
+ temp_val += sensor_reading * coefficient;
+ break;
+ case FormulaOption::MAXIMUM:
+ if (i == 0)
+ temp_val = std::numeric_limits<float>::lowest();
+ if (sensor_reading * coefficient > temp_val)
+ temp_val = sensor_reading * coefficient;
+ break;
+ case FormulaOption::MINIMUM:
+ if (i == 0)
+ temp_val = std::numeric_limits<float>::max();
+ if (sensor_reading * coefficient < temp_val)
+ temp_val = sensor_reading * coefficient;
+ break;
+ default:
+ break;
+ }
+ }
+ *temp = (temp_val + sensor_info.virtual_sensor_info->offset);
+ }
+ (*sensor_log_map)[sensor_name.data()] = *temp;
+ ATRACE_INT(sensor_name.data(), static_cast<int>(*temp));
+
+ {
+ std::unique_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ sensor_status.thermal_cached.temp = *temp;
+ sensor_status.thermal_cached.timestamp = now;
+ }
+ auto real_temp = (*temp) * sensor_info.multiplier;
+ thermal_stats_helper_.updateSensorTempStatsByThreshold(sensor_name, real_temp);
+ return true;
+}
+
+// This is called in the different thread context and will update sensor_status
+// uevent_sensors is the set of sensors which trigger uevent from thermal core driver.
+std::chrono::milliseconds ThermalHelper::thermalWatcherCallbackFunc(
+ const std::set<std::string> &uevent_sensors) {
+ std::vector<Temperature> temps;
+ std::vector<std::string> cooling_devices_to_update;
+ boot_clock::time_point now = boot_clock::now();
+ auto min_sleep_ms = std::chrono::milliseconds::max();
+ bool power_data_is_updated = false;
+
+ ATRACE_CALL();
+ for (auto &name_status_pair : sensor_status_map_) {
+ bool force_update = false;
+ bool force_no_cache = false;
+ Temperature temp;
+ TemperatureThreshold threshold;
+ SensorStatus &sensor_status = name_status_pair.second;
+ const SensorInfo &sensor_info = sensor_info_map_.at(name_status_pair.first);
+
+ // Only handle the sensors in allow list
+ if (!sensor_info.is_watch) {
+ continue;
+ }
+
+ ATRACE_NAME(StringPrintf("ThermalHelper::thermalWatcherCallbackFunc - %s",
+ name_status_pair.first.data())
+ .c_str());
+
+ std::chrono::milliseconds time_elapsed_ms = std::chrono::milliseconds::zero();
+ auto sleep_ms = (sensor_status.severity != ThrottlingSeverity::NONE)
+ ? sensor_info.passive_delay
+ : sensor_info.polling_delay;
+
+ if (sensor_info.virtual_sensor_info != nullptr &&
+ !sensor_info.virtual_sensor_info->trigger_sensors.empty()) {
+ for (size_t i = 0; i < sensor_info.virtual_sensor_info->trigger_sensors.size(); i++) {
+ const auto &trigger_sensor_status =
+ sensor_status_map_.at(sensor_info.virtual_sensor_info->trigger_sensors[i]);
+ if (trigger_sensor_status.severity != ThrottlingSeverity::NONE) {
+ sleep_ms = sensor_info.passive_delay;
+ break;
+ }
+ }
+ }
+ // Check if the sensor need to be updated
+ if (sensor_status.last_update_time == boot_clock::time_point::min()) {
+ force_update = true;
+ } else {
+ time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+ now - sensor_status.last_update_time);
+ if (uevent_sensors.size()) {
+ if (sensor_info.virtual_sensor_info != nullptr) {
+ for (size_t i = 0; i < sensor_info.virtual_sensor_info->trigger_sensors.size();
+ i++) {
+ if (uevent_sensors.find(
+ sensor_info.virtual_sensor_info->trigger_sensors[i]) !=
+ uevent_sensors.end()) {
+ force_update = true;
+ break;
+ }
+ }
+ } else if (uevent_sensors.find(name_status_pair.first) != uevent_sensors.end()) {
+ force_update = true;
+ force_no_cache = true;
+ }
+ } else if (time_elapsed_ms > sleep_ms) {
+ force_update = true;
+ }
+ }
+ {
+ std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ if (sensor_status.emul_setting != nullptr &&
+ sensor_status.emul_setting->pending_update) {
+ force_update = true;
+ sensor_status.emul_setting->pending_update = false;
+ LOG(INFO) << "Update " << name_status_pair.first.data()
+ << " right away with emul setting";
+ }
+ }
+ LOG(VERBOSE) << "sensor " << name_status_pair.first
+ << ": time_elapsed=" << time_elapsed_ms.count()
+ << ", sleep_ms=" << sleep_ms.count() << ", force_update = " << force_update
+ << ", force_no_cache = " << force_no_cache;
+
+ if (!force_update) {
+ auto timeout_remaining = sleep_ms - time_elapsed_ms;
+ if (min_sleep_ms > timeout_remaining) {
+ min_sleep_ms = timeout_remaining;
+ }
+ LOG(VERBOSE) << "sensor " << name_status_pair.first
+ << ": timeout_remaining=" << timeout_remaining.count();
+ continue;
+ }
+
+ std::pair<ThrottlingSeverity, ThrottlingSeverity> throttling_status;
+ if (!readTemperature(name_status_pair.first, &temp, &throttling_status, force_no_cache)) {
+ LOG(ERROR) << __func__
+ << ": error reading temperature for sensor: " << name_status_pair.first;
+ continue;
+ }
+ if (!readTemperatureThreshold(name_status_pair.first, &threshold)) {
+ LOG(ERROR) << __func__ << ": error reading temperature threshold for sensor: "
+ << name_status_pair.first;
+ continue;
+ }
+
+ {
+ // writer lock
+ std::unique_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+ if (throttling_status.first != sensor_status.prev_hot_severity) {
+ sensor_status.prev_hot_severity = throttling_status.first;
+ }
+ if (throttling_status.second != sensor_status.prev_cold_severity) {
+ sensor_status.prev_cold_severity = throttling_status.second;
+ }
+ if (temp.throttlingStatus != sensor_status.severity) {
+ temps.push_back(temp);
+ sensor_status.severity = temp.throttlingStatus;
+ sleep_ms = (sensor_status.severity != ThrottlingSeverity::NONE)
+ ? sensor_info.passive_delay
+ : sensor_info.polling_delay;
+ }
+ }
+
+ if (!power_data_is_updated) {
+ power_files_.refreshPowerStatus();
+ power_data_is_updated = true;
+ }
+
+ if (sensor_status.severity == ThrottlingSeverity::NONE) {
+ thermal_throttling_.clearThrottlingData(name_status_pair.first, sensor_info);
+ } else {
+ // update thermal throttling request
+ thermal_throttling_.thermalThrottlingUpdate(
+ temp, sensor_info, sensor_status.severity, time_elapsed_ms,
+ power_files_.GetPowerStatusMap(), cooling_device_info_map_);
+ }
+
+ thermal_throttling_.computeCoolingDevicesRequest(
+ name_status_pair.first, sensor_info, sensor_status.severity,
+ &cooling_devices_to_update, &thermal_stats_helper_);
+ if (min_sleep_ms > sleep_ms) {
+ min_sleep_ms = sleep_ms;
+ }
+
+ LOG(VERBOSE) << "Sensor " << name_status_pair.first << ": sleep_ms=" << sleep_ms.count()
+ << ", min_sleep_ms voting result=" << min_sleep_ms.count();
+ sensor_status.last_update_time = now;
+ }
+
+ if (!cooling_devices_to_update.empty()) {
+ updateCoolingDevices(cooling_devices_to_update);
+ }
+
+ if (!temps.empty()) {
+ for (const auto &t : temps) {
+ if (sensor_info_map_.at(t.name).send_cb && cb_) {
+ cb_(t);
+ }
+
+ if (sensor_info_map_.at(t.name).send_powerhint && isAidlPowerHalExist()) {
+ sendPowerExtHint(t);
+ }
+ }
+ }
+
+ int count_failed_reporting = thermal_stats_helper_.reportStats();
+ if (count_failed_reporting != 0) {
+ LOG(ERROR) << "Failed to report " << count_failed_reporting << " thermal stats";
+ }
+
+ return min_sleep_ms;
+}
+
+bool ThermalHelper::connectToPowerHal() {
+ return power_hal_service_.connect();
+}
+
+void ThermalHelper::updateSupportedPowerHints() {
+ for (auto const &name_status_pair : sensor_info_map_) {
+ if (!(name_status_pair.second.send_powerhint)) {
+ continue;
+ }
+ ThrottlingSeverity current_severity = ThrottlingSeverity::NONE;
+ for (const auto &severity : ::ndk::enum_range<ThrottlingSeverity>()) {
+ if (severity == ThrottlingSeverity::NONE) {
+ supported_powerhint_map_[name_status_pair.first][ThrottlingSeverity::NONE] =
+ ThrottlingSeverity::NONE;
+ continue;
+ }
+
+ bool isSupported = false;
+ ndk::ScopedAStatus isSupportedResult;
+
+ if (power_hal_service_.isPowerHalExtConnected()) {
+ isSupported = power_hal_service_.isModeSupported(name_status_pair.first, severity);
+ }
+ if (isSupported)
+ current_severity = severity;
+ supported_powerhint_map_[name_status_pair.first][severity] = current_severity;
+ }
+ }
+}
+
+void ThermalHelper::sendPowerExtHint(const Temperature &t) {
+ ATRACE_CALL();
+ std::lock_guard<std::shared_mutex> lock(sensor_status_map_mutex_);
+ ThrottlingSeverity prev_hint_severity;
+ prev_hint_severity = sensor_status_map_.at(t.name).prev_hint_severity;
+ ThrottlingSeverity current_hint_severity = supported_powerhint_map_[t.name][t.throttlingStatus];
+
+ if (prev_hint_severity == current_hint_severity)
+ return;
+
+ if (prev_hint_severity != ThrottlingSeverity::NONE) {
+ power_hal_service_.setMode(t.name, prev_hint_severity, false);
+ }
+
+ if (current_hint_severity != ThrottlingSeverity::NONE) {
+ power_hal_service_.setMode(t.name, current_hint_severity, true);
+ }
+
+ sensor_status_map_[t.name].prev_hint_severity = current_hint_severity;
+}
+} // namespace implementation
+} // namespace thermal
+} // namespace hardware
+} // namespace android
+} // namespace aidl
--- /dev/null
+/*
+ * 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
--- /dev/null
+{
+ "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":"^(.+)$"
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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_info.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <json/reader.h>
+
+#include <cmath>
+#include <unordered_set>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr std::string_view kPowerLinkDisabledProperty("vendor.disable.thermal.powerlink");
+
+namespace {
+
+template <typename T>
+// Return false when failed parsing
+bool getTypeFromString(std::string_view str, T *out) {
+ auto types = ::ndk::enum_range<T>();
+ for (const auto &type : types) {
+ if (::aidl::android::hardware::thermal::toString(type) == str) {
+ *out = type;
+ return true;
+ }
+ }
+ return false;
+}
+
+float getFloatFromValue(const Json::Value &value) {
+ if (value.isString()) {
+ return std::stof(value.asString());
+ } else {
+ return value.asFloat();
+ }
+}
+
+int getIntFromValue(const Json::Value &value) {
+ if (value.isString()) {
+ return (value.asString() == "max") ? std::numeric_limits<int>::max()
+ : std::stoul(value.asString());
+ } else {
+ return value.asInt();
+ }
+}
+
+bool getIntFromJsonValues(const Json::Value &values, CdevArray *out, bool inc_check,
+ bool dec_check) {
+ CdevArray ret;
+
+ if (inc_check && dec_check) {
+ LOG(ERROR) << "Cannot enable inc_check and dec_check at the same time";
+ return false;
+ }
+
+ if (values.size() != kThrottlingSeverityCount) {
+ LOG(ERROR) << "Values size is invalid";
+ return false;
+ } else {
+ int last;
+ for (Json::Value::ArrayIndex i = 0; i < kThrottlingSeverityCount; ++i) {
+ ret[i] = getIntFromValue(values[i]);
+ if (inc_check && ret[i] < last) {
+ LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " min=" << last;
+ return false;
+ }
+ if (dec_check && ret[i] > last) {
+ LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " max=" << last;
+ return false;
+ }
+ last = ret[i];
+ LOG(INFO) << "[" << i << "]: " << ret[i];
+ }
+ }
+
+ *out = ret;
+ return true;
+}
+
+bool getFloatFromJsonValues(const Json::Value &values, ThrottlingArray *out, bool inc_check,
+ bool dec_check) {
+ ThrottlingArray ret;
+
+ if (inc_check && dec_check) {
+ LOG(ERROR) << "Cannot enable inc_check and dec_check at the same time";
+ return false;
+ }
+
+ if (values.size() != kThrottlingSeverityCount) {
+ LOG(ERROR) << "Values size is invalid";
+ return false;
+ } else {
+ float last = std::nanf("");
+ for (Json::Value::ArrayIndex i = 0; i < kThrottlingSeverityCount; ++i) {
+ ret[i] = getFloatFromValue(values[i]);
+ if (inc_check && !std::isnan(last) && !std::isnan(ret[i]) && ret[i] < last) {
+ LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " min=" << last;
+ return false;
+ }
+ if (dec_check && !std::isnan(last) && !std::isnan(ret[i]) && ret[i] > last) {
+ LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " max=" << last;
+ return false;
+ }
+ last = std::isnan(ret[i]) ? last : ret[i];
+ LOG(INFO) << "[" << i << "]: " << ret[i];
+ }
+ }
+
+ *out = ret;
+ return true;
+}
+} // namespace
+
+bool ParseThermalConfig(std::string_view config_path, Json::Value *config) {
+ std::string json_doc;
+ if (!::android::base::ReadFileToString(config_path.data(), &json_doc)) {
+ LOG(ERROR) << "Failed to read JSON config from " << config_path;
+ return false;
+ }
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+ std::string errorMessage;
+ if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), config, &errorMessage)) {
+ LOG(ERROR) << "Failed to parse JSON config: " << errorMessage;
+ return false;
+ }
+ return true;
+}
+
+bool ParseVirtualSensorInfo(const std::string_view name, const Json::Value &sensor,
+ std::unique_ptr<VirtualSensorInfo> *virtual_sensor_info) {
+ if (sensor["VirtualSensor"].empty() || !sensor["VirtualSensor"].isBool()) {
+ LOG(INFO) << "Failed to read Sensor[" << name << "]'s VirtualSensor";
+ return true;
+ }
+ bool is_virtual_sensor = sensor["VirtualSensor"].asBool();
+ LOG(INFO) << "Sensor[" << name << "]'s' VirtualSensor: " << is_virtual_sensor;
+ if (!is_virtual_sensor) {
+ return true;
+ }
+ float offset = 0;
+ std::vector<std::string> linked_sensors;
+ std::vector<SensorFusionType> linked_sensors_type;
+ std::vector<std::string> trigger_sensors;
+ std::vector<float> coefficients;
+ FormulaOption formula = FormulaOption::COUNT_THRESHOLD;
+
+ Json::Value values = sensor["Combination"];
+ if (values.size()) {
+ linked_sensors.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ linked_sensors.emplace_back(values[j].asString());
+ LOG(INFO) << "Sensor[" << name << "]'s Combination[" << j << "]: " << linked_sensors[j];
+ }
+ } else {
+ LOG(ERROR) << "Sensor[" << name << "] has no Combination setting";
+ return false;
+ }
+
+ values = sensor["CombinationType"];
+ if (!values.size()) {
+ linked_sensors_type.reserve(linked_sensors.size());
+ for (size_t j = 0; j < linked_sensors.size(); ++j) {
+ linked_sensors_type.emplace_back(SensorFusionType::SENSOR);
+ }
+ } else if (values.size() != linked_sensors.size()) {
+ LOG(ERROR) << "Sensor[" << name << "] has invalid CombinationType size";
+ return false;
+ } else {
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ if (values[j].asString().compare("SENSOR") == 0) {
+ linked_sensors_type.emplace_back(SensorFusionType::SENSOR);
+ } else if (values[j].asString().compare("ODPM") == 0) {
+ linked_sensors_type.emplace_back(SensorFusionType::ODPM);
+ } else {
+ LOG(ERROR) << "Sensor[" << name << "] has invalid CombinationType settings "
+ << values[j].asString();
+ return false;
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s CombinationType[" << j
+ << "]: " << linked_sensors_type[j];
+ }
+ }
+
+ values = sensor["Coefficient"];
+ if (values.size()) {
+ coefficients.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ coefficients.emplace_back(getFloatFromValue(values[j]));
+ LOG(INFO) << "Sensor[" << name << "]'s coefficient[" << j << "]: " << coefficients[j];
+ }
+ } else {
+ LOG(ERROR) << "Sensor[" << name << "] has no Coefficient setting";
+ return false;
+ }
+ if (linked_sensors.size() != coefficients.size()) {
+ LOG(ERROR) << "Sensor[" << name << "] has invalid Coefficient size";
+ return false;
+ }
+ if (!sensor["Offset"].empty()) {
+ offset = sensor["Offset"].asFloat();
+ }
+
+ values = sensor["TriggerSensor"];
+ if (!values.empty()) {
+ if (values.isString()) {
+ trigger_sensors.emplace_back(values.asString());
+ LOG(INFO) << "Sensor[" << name << "]'s TriggerSensor: " << values.asString();
+ } else if (values.size()) {
+ trigger_sensors.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ if (!values[j].isString()) {
+ LOG(ERROR) << name << " TriggerSensor should be an array of string";
+ return false;
+ }
+ trigger_sensors.emplace_back(values[j].asString());
+ LOG(INFO) << "Sensor[" << name << "]'s TriggerSensor[" << j
+ << "]: " << trigger_sensors[j];
+ }
+ } else {
+ LOG(ERROR) << "Sensor[" << name << "]'s TriggerSensor should be a string";
+ return false;
+ }
+ }
+
+ if (sensor["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
+ formula = FormulaOption::COUNT_THRESHOLD;
+ } else if (sensor["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
+ formula = FormulaOption::WEIGHTED_AVG;
+ } else if (sensor["Formula"].asString().compare("MAXIMUM") == 0) {
+ formula = FormulaOption::MAXIMUM;
+ } else if (sensor["Formula"].asString().compare("MINIMUM") == 0) {
+ formula = FormulaOption::MINIMUM;
+ } else {
+ LOG(ERROR) << "Sensor[" << name << "]'s Formula is invalid";
+ return false;
+ }
+ virtual_sensor_info->reset(new VirtualSensorInfo{
+ linked_sensors, linked_sensors_type, coefficients, offset, trigger_sensors, formula});
+ return true;
+}
+
+bool ParseBindedCdevInfo(const Json::Value &values,
+ std::unordered_map<std::string, BindedCdevInfo> *binded_cdev_info_map,
+ const bool support_pid, bool *support_hard_limit) {
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ Json::Value sub_values;
+ const std::string &cdev_name = values[j]["CdevRequest"].asString();
+ ThrottlingArray cdev_weight_for_pid;
+ cdev_weight_for_pid.fill(NAN);
+ CdevArray cdev_ceiling;
+ cdev_ceiling.fill(std::numeric_limits<int>::max());
+ int max_release_step = std::numeric_limits<int>::max();
+ int max_throttle_step = std::numeric_limits<int>::max();
+ if (support_pid) {
+ if (!values[j]["CdevWeightForPID"].empty()) {
+ LOG(INFO) << "Star to parse " << cdev_name << "'s CdevWeightForPID";
+ if (!getFloatFromJsonValues(values[j]["CdevWeightForPID"], &cdev_weight_for_pid,
+ false, false)) {
+ LOG(ERROR) << "Failed to parse CdevWeightForPID";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+ if (!values[j]["CdevCeiling"].empty()) {
+ LOG(INFO) << "Start to parse CdevCeiling: " << cdev_name;
+ if (!getIntFromJsonValues(values[j]["CdevCeiling"], &cdev_ceiling, false, false)) {
+ LOG(ERROR) << "Failed to parse CdevCeiling";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+
+ if (!values[j]["MaxReleaseStep"].empty()) {
+ max_release_step = getIntFromValue(values[j]["MaxReleaseStep"]);
+ if (max_release_step < 0) {
+ LOG(ERROR) << cdev_name << " MaxReleaseStep: " << max_release_step;
+ binded_cdev_info_map->clear();
+ return false;
+ } else {
+ LOG(INFO) << cdev_name << " MaxReleaseStep: " << max_release_step;
+ }
+ }
+ if (!values[j]["MaxThrottleStep"].empty()) {
+ max_throttle_step = getIntFromValue(values[j]["MaxThrottleStep"]);
+ if (max_throttle_step < 0) {
+ LOG(ERROR) << cdev_name << " MaxThrottleStep: " << max_throttle_step;
+ binded_cdev_info_map->clear();
+ return false;
+ } else {
+ LOG(INFO) << cdev_name << " MaxThrottleStep: " << max_throttle_step;
+ }
+ }
+ }
+ CdevArray limit_info;
+ limit_info.fill(0);
+ ThrottlingArray power_thresholds;
+ power_thresholds.fill(NAN);
+ ReleaseLogic release_logic = ReleaseLogic::NONE;
+
+ sub_values = values[j]["LimitInfo"];
+ if (sub_values.size()) {
+ LOG(INFO) << "Start to parse LimitInfo: " << cdev_name;
+ if (!getIntFromJsonValues(sub_values, &limit_info, false, false)) {
+ LOG(ERROR) << "Failed to parse LimitInfo";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ *support_hard_limit = true;
+ }
+ // Parse linked power info
+ std::string power_rail;
+ bool high_power_check = false;
+ bool throttling_with_power_link = false;
+ CdevArray cdev_floor_with_power_link;
+ cdev_floor_with_power_link.fill(0);
+
+ const bool power_link_disabled =
+ ::android::base::GetBoolProperty(kPowerLinkDisabledProperty.data(), false);
+ if (!power_link_disabled) {
+ power_rail = values[j]["BindedPowerRail"].asString();
+
+ if (values[j]["HighPowerCheck"].asBool()) {
+ high_power_check = true;
+ }
+ LOG(INFO) << "Highpowercheck: " << std::boolalpha << high_power_check;
+
+ if (values[j]["ThrottlingWithPowerLink"].asBool()) {
+ throttling_with_power_link = true;
+ }
+ LOG(INFO) << "ThrottlingwithPowerLink: " << std::boolalpha
+ << throttling_with_power_link;
+
+ sub_values = values[j]["CdevFloorWithPowerLink"];
+ if (sub_values.size()) {
+ LOG(INFO) << "Start to parse " << cdev_name << "'s CdevFloorWithPowerLink";
+ if (!getIntFromJsonValues(sub_values, &cdev_floor_with_power_link, false, false)) {
+ LOG(ERROR) << "Failed to parse CdevFloor";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+ sub_values = values[j]["PowerThreshold"];
+ if (sub_values.size()) {
+ LOG(INFO) << "Start to parse " << cdev_name << "'s PowerThreshold";
+ if (!getFloatFromJsonValues(sub_values, &power_thresholds, false, false)) {
+ LOG(ERROR) << "Failed to parse power thresholds";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ if (values[j]["ReleaseLogic"].asString() == "INCREASE") {
+ release_logic = ReleaseLogic::INCREASE;
+ LOG(INFO) << "Release logic: INCREASE";
+ } else if (values[j]["ReleaseLogic"].asString() == "DECREASE") {
+ release_logic = ReleaseLogic::DECREASE;
+ LOG(INFO) << "Release logic: DECREASE";
+ } else if (values[j]["ReleaseLogic"].asString() == "STEPWISE") {
+ release_logic = ReleaseLogic::STEPWISE;
+ LOG(INFO) << "Release logic: STEPWISE";
+ } else if (values[j]["ReleaseLogic"].asString() == "RELEASE_TO_FLOOR") {
+ release_logic = ReleaseLogic::RELEASE_TO_FLOOR;
+ LOG(INFO) << "Release logic: RELEASE_TO_FLOOR";
+ } else {
+ LOG(ERROR) << "Release logic is invalid";
+ binded_cdev_info_map->clear();
+ return false;
+ }
+ }
+ }
+
+ (*binded_cdev_info_map)[cdev_name] = {
+ .limit_info = limit_info,
+ .power_thresholds = power_thresholds,
+ .release_logic = release_logic,
+ .high_power_check = high_power_check,
+ .throttling_with_power_link = throttling_with_power_link,
+ .cdev_weight_for_pid = cdev_weight_for_pid,
+ .cdev_ceiling = cdev_ceiling,
+ .max_release_step = max_release_step,
+ .max_throttle_step = max_throttle_step,
+ .cdev_floor_with_power_link = cdev_floor_with_power_link,
+ .power_rail = power_rail,
+ };
+ }
+ return true;
+}
+
+bool ParseSensorThrottlingInfo(const std::string_view name, const Json::Value &sensor,
+ bool *support_throttling,
+ std::shared_ptr<ThrottlingInfo> *throttling_info) {
+ std::array<float, kThrottlingSeverityCount> k_po;
+ k_po.fill(0.0);
+ std::array<float, kThrottlingSeverityCount> k_pu;
+ k_pu.fill(0.0);
+ std::array<float, kThrottlingSeverityCount> k_i;
+ k_i.fill(0.0);
+ std::array<float, kThrottlingSeverityCount> k_d;
+ k_d.fill(0.0);
+ std::array<float, kThrottlingSeverityCount> i_max;
+ i_max.fill(NAN);
+ std::array<float, kThrottlingSeverityCount> max_alloc_power;
+ max_alloc_power.fill(NAN);
+ std::array<float, kThrottlingSeverityCount> min_alloc_power;
+ min_alloc_power.fill(NAN);
+ std::array<float, kThrottlingSeverityCount> s_power;
+ s_power.fill(NAN);
+ std::array<float, kThrottlingSeverityCount> i_cutoff;
+ i_cutoff.fill(NAN);
+ float i_default = 0.0;
+ int tran_cycle = 0;
+ bool support_pid = false;
+ bool support_hard_limit = false;
+
+ // Parse PID parameters
+ if (!sensor["PIDInfo"].empty()) {
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s K_Po";
+ if (sensor["PIDInfo"]["K_Po"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["K_Po"], &k_po, false, false)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_Po";
+ return false;
+ }
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s K_Pu";
+ if (sensor["PIDInfo"]["K_Pu"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["K_Pu"], &k_pu, false, false)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_Pu";
+ return false;
+ }
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s K_I";
+ if (sensor["PIDInfo"]["K_I"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["K_I"], &k_i, false, false)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_I";
+ return false;
+ }
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s K_D";
+ if (sensor["PIDInfo"]["K_D"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["K_D"], &k_d, false, false)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_D";
+ return false;
+ }
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s I_Max";
+ if (sensor["PIDInfo"]["I_Max"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["I_Max"], &i_max, false, false)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse I_Max";
+ return false;
+ }
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s MaxAllocPower";
+ if (sensor["PIDInfo"]["MaxAllocPower"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["MaxAllocPower"], &max_alloc_power, false,
+ true)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse MaxAllocPower";
+ return false;
+ }
+ LOG(INFO) << "Start to parse"
+ << " Sensor[" << name << "]'s MinAllocPower";
+ if (sensor["PIDInfo"]["MinAllocPower"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["MinAllocPower"], &min_alloc_power, false,
+ true)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse MinAllocPower";
+ return false;
+ }
+ LOG(INFO) << "Start to parse Sensor[" << name << "]'s S_Power";
+ if (sensor["PIDInfo"]["S_Power"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["S_Power"], &s_power, false, true)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse S_Power";
+ return false;
+ }
+ LOG(INFO) << "Start to parse Sensor[" << name << "]'s I_Cutoff";
+ if (sensor["PIDInfo"]["I_Cutoff"].empty() ||
+ !getFloatFromJsonValues(sensor["PIDInfo"]["I_Cutoff"], &i_cutoff, false, false)) {
+ LOG(ERROR) << "Sensor[" << name << "]: Failed to parse I_Cutoff";
+ return false;
+ }
+ i_default = getFloatFromValue(sensor["PIDInfo"]["I_Default"]);
+ LOG(INFO) << "Sensor[" << name << "]'s I_Default: " << i_default;
+
+ tran_cycle = getFloatFromValue(sensor["PIDInfo"]["TranCycle"]);
+ LOG(INFO) << "Sensor[" << name << "]'s TranCycle: " << tran_cycle;
+
+ // Confirm we have at least one valid PID combination
+ bool valid_pid_combination = false;
+ for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+ if (!std::isnan(s_power[j])) {
+ if (std::isnan(k_po[j]) || std::isnan(k_pu[j]) || std::isnan(k_i[j]) ||
+ std::isnan(k_d[j]) || std::isnan(i_max[j]) || std::isnan(max_alloc_power[j]) ||
+ std::isnan(min_alloc_power[j]) || std::isnan(i_cutoff[j])) {
+ valid_pid_combination = false;
+ break;
+ } else {
+ valid_pid_combination = true;
+ }
+ }
+ }
+ if (!valid_pid_combination) {
+ LOG(ERROR) << "Sensor[" << name << "]: Invalid PID parameters combinations";
+ return false;
+ } else {
+ support_pid = true;
+ }
+ }
+
+ // Parse binded cooling device
+ std::unordered_map<std::string, BindedCdevInfo> binded_cdev_info_map;
+ if (!ParseBindedCdevInfo(sensor["BindedCdevInfo"], &binded_cdev_info_map, support_pid,
+ &support_hard_limit)) {
+ LOG(ERROR) << "Sensor[" << name << "]: failed to parse BindedCdevInfo";
+ return false;
+ }
+
+ std::unordered_map<std::string, ThrottlingArray> excluded_power_info_map;
+ Json::Value values = sensor["ExcludedPowerInfo"];
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ Json::Value sub_values;
+ const std::string &power_rail = values[j]["PowerRail"].asString();
+ if (power_rail.empty()) {
+ LOG(ERROR) << "Sensor[" << name << "] failed to parse excluded PowerRail";
+ return false;
+ }
+ ThrottlingArray power_weight;
+ power_weight.fill(1);
+ if (!values[j]["PowerWeight"].empty()) {
+ LOG(INFO) << "Sensor[" << name << "]: Start to parse " << power_rail
+ << "'s PowerWeight";
+ if (!getFloatFromJsonValues(values[j]["PowerWeight"], &power_weight, false, false)) {
+ LOG(ERROR) << "Failed to parse PowerWeight";
+ return false;
+ }
+ }
+ excluded_power_info_map[power_rail] = power_weight;
+ }
+ throttling_info->reset(new ThrottlingInfo{
+ k_po, k_pu, k_i, k_d, i_max, max_alloc_power, min_alloc_power, s_power, i_cutoff,
+ i_default, tran_cycle, excluded_power_info_map, binded_cdev_info_map});
+ *support_throttling = support_pid | support_hard_limit;
+ return true;
+}
+
+bool ParseSensorInfo(const Json::Value &config,
+ std::unordered_map<std::string, SensorInfo> *sensors_parsed) {
+ Json::Value sensors = config["Sensors"];
+ std::size_t total_parsed = 0;
+ std::unordered_set<std::string> sensors_name_parsed;
+
+ for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) {
+ const std::string &name = sensors[i]["Name"].asString();
+ LOG(INFO) << "Sensor[" << i << "]'s Name: " << name;
+ if (name.empty()) {
+ LOG(ERROR) << "Failed to read Sensor[" << i << "]'s Name";
+ sensors_parsed->clear();
+ return false;
+ }
+
+ auto result = sensors_name_parsed.insert(name);
+ if (!result.second) {
+ LOG(ERROR) << "Duplicate Sensor[" << i << "]'s Name";
+ sensors_parsed->clear();
+ return false;
+ }
+
+ std::string sensor_type_str = sensors[i]["Type"].asString();
+ LOG(INFO) << "Sensor[" << name << "]'s Type: " << sensor_type_str;
+ TemperatureType sensor_type;
+
+ if (!getTypeFromString(sensor_type_str, &sensor_type)) {
+ LOG(ERROR) << "Invalid Sensor[" << name << "]'s Type: " << sensor_type_str;
+ sensors_parsed->clear();
+ return false;
+ }
+
+ bool send_cb = false;
+ if (!sensors[i]["Monitor"].empty() && sensors[i]["Monitor"].isBool()) {
+ send_cb = sensors[i]["Monitor"].asBool();
+ } else if (!sensors[i]["SendCallback"].empty() && sensors[i]["SendCallback"].isBool()) {
+ send_cb = sensors[i]["SendCallback"].asBool();
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s SendCallback: " << std::boolalpha << send_cb
+ << std::noboolalpha;
+
+ bool send_powerhint = false;
+ if (sensors[i]["SendPowerHint"].empty() || !sensors[i]["SendPowerHint"].isBool()) {
+ LOG(INFO) << "Failed to read Sensor[" << name << "]'s SendPowerHint, set to 'false'";
+ } else if (sensors[i]["SendPowerHint"].asBool()) {
+ send_powerhint = true;
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s SendPowerHint: " << std::boolalpha << send_powerhint
+ << std::noboolalpha;
+
+ bool is_hidden = false;
+ if (sensors[i]["Hidden"].empty() || !sensors[i]["Hidden"].isBool()) {
+ LOG(INFO) << "Failed to read Sensor[" << name << "]'s Hidden, set to 'false'";
+ } else if (sensors[i]["Hidden"].asBool()) {
+ is_hidden = true;
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s Hidden: " << std::boolalpha << is_hidden
+ << std::noboolalpha;
+
+ std::array<float, kThrottlingSeverityCount> hot_thresholds;
+ hot_thresholds.fill(NAN);
+ std::array<float, kThrottlingSeverityCount> cold_thresholds;
+ cold_thresholds.fill(NAN);
+ std::array<float, kThrottlingSeverityCount> hot_hysteresis;
+ hot_hysteresis.fill(0.0);
+ std::array<float, kThrottlingSeverityCount> cold_hysteresis;
+ cold_hysteresis.fill(0.0);
+
+ Json::Value values = sensors[i]["HotThreshold"];
+ if (!values.size()) {
+ LOG(INFO) << "Sensor[" << name << "]'s HotThreshold, default all to NAN";
+ } else if (values.size() != kThrottlingSeverityCount) {
+ LOG(ERROR) << "Invalid Sensor[" << name << "]'s HotThreshold count:" << values.size();
+ sensors_parsed->clear();
+ return false;
+ } else {
+ float min = std::numeric_limits<float>::min();
+ for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+ hot_thresholds[j] = getFloatFromValue(values[j]);
+ if (!std::isnan(hot_thresholds[j])) {
+ if (hot_thresholds[j] < min) {
+ LOG(ERROR) << "Invalid "
+ << "Sensor[" << name << "]'s HotThreshold[j" << j
+ << "]: " << hot_thresholds[j] << " < " << min;
+ sensors_parsed->clear();
+ return false;
+ }
+ min = hot_thresholds[j];
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s HotThreshold[" << j
+ << "]: " << hot_thresholds[j];
+ }
+ }
+
+ values = sensors[i]["HotHysteresis"];
+ if (!values.size()) {
+ LOG(INFO) << "Sensor[" << name << "]'s HotHysteresis, default all to 0.0";
+ } else if (values.size() != kThrottlingSeverityCount) {
+ LOG(ERROR) << "Invalid Sensor[" << name << "]'s HotHysteresis, count:" << values.size();
+ sensors_parsed->clear();
+ return false;
+ } else {
+ for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+ hot_hysteresis[j] = getFloatFromValue(values[j]);
+ if (std::isnan(hot_hysteresis[j])) {
+ LOG(ERROR) << "Invalid Sensor[" << name
+ << "]'s HotHysteresis: " << hot_hysteresis[j];
+ sensors_parsed->clear();
+ return false;
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s HotHysteresis[" << j
+ << "]: " << hot_hysteresis[j];
+ }
+ }
+
+ for (Json::Value::ArrayIndex j = 0; j < (kThrottlingSeverityCount - 1); ++j) {
+ if (std::isnan(hot_thresholds[j])) {
+ continue;
+ }
+ for (auto k = j + 1; k < kThrottlingSeverityCount; ++k) {
+ if (std::isnan(hot_thresholds[k])) {
+ continue;
+ } else if (hot_thresholds[j] > (hot_thresholds[k] - hot_hysteresis[k])) {
+ LOG(ERROR) << "Sensor[" << name << "]'s hot threshold " << j
+ << " is overlapped";
+ sensors_parsed->clear();
+ return false;
+ } else {
+ break;
+ }
+ }
+ }
+
+ values = sensors[i]["ColdThreshold"];
+ if (!values.size()) {
+ LOG(INFO) << "Sensor[" << name << "]'s ColdThreshold, default all to NAN";
+ } else if (values.size() != kThrottlingSeverityCount) {
+ LOG(ERROR) << "Invalid Sensor[" << name << "]'s ColdThreshold count:" << values.size();
+ sensors_parsed->clear();
+ return false;
+ } else {
+ float max = std::numeric_limits<float>::max();
+ for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+ cold_thresholds[j] = getFloatFromValue(values[j]);
+ if (!std::isnan(cold_thresholds[j])) {
+ if (cold_thresholds[j] > max) {
+ LOG(ERROR) << "Invalid "
+ << "Sensor[" << name << "]'s ColdThreshold[j" << j
+ << "]: " << cold_thresholds[j] << " > " << max;
+ sensors_parsed->clear();
+ return false;
+ }
+ max = cold_thresholds[j];
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s ColdThreshold[" << j
+ << "]: " << cold_thresholds[j];
+ }
+ }
+
+ values = sensors[i]["ColdHysteresis"];
+ if (!values.size()) {
+ LOG(INFO) << "Sensor[" << name << "]'s ColdHysteresis, default all to 0.0";
+ } else if (values.size() != kThrottlingSeverityCount) {
+ LOG(ERROR) << "Invalid Sensor[" << name << "]'s ColdHysteresis count:" << values.size();
+ sensors_parsed->clear();
+ return false;
+ } else {
+ for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+ cold_hysteresis[j] = getFloatFromValue(values[j]);
+ if (std::isnan(cold_hysteresis[j])) {
+ LOG(ERROR) << "Invalid Sensor[" << name
+ << "]'s ColdHysteresis: " << cold_hysteresis[j];
+ sensors_parsed->clear();
+ return false;
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s ColdHysteresis[" << j
+ << "]: " << cold_hysteresis[j];
+ }
+ }
+
+ for (Json::Value::ArrayIndex j = 0; j < (kThrottlingSeverityCount - 1); ++j) {
+ if (std::isnan(cold_thresholds[j])) {
+ continue;
+ }
+ for (auto k = j + 1; k < kThrottlingSeverityCount; ++k) {
+ if (std::isnan(cold_thresholds[k])) {
+ continue;
+ } else if (cold_thresholds[j] < (cold_thresholds[k] + cold_hysteresis[k])) {
+ LOG(ERROR) << "Sensor[" << name << "]'s cold threshold " << j
+ << " is overlapped";
+ sensors_parsed->clear();
+ return false;
+ } else {
+ break;
+ }
+ }
+ }
+
+ std::string temp_path;
+ if (!sensors[i]["TempPath"].empty()) {
+ temp_path = sensors[i]["TempPath"].asString();
+ LOG(INFO) << "Sensor[" << name << "]'s TempPath: " << temp_path;
+ }
+
+ float vr_threshold = NAN;
+ if (!sensors[i]["VrThreshold"].empty()) {
+ vr_threshold = getFloatFromValue(sensors[i]["VrThreshold"]);
+ LOG(INFO) << "Sensor[" << name << "]'s VrThreshold: " << vr_threshold;
+ }
+ float multiplier = sensors[i]["Multiplier"].asFloat();
+ LOG(INFO) << "Sensor[" << name << "]'s Multiplier: " << multiplier;
+
+ std::chrono::milliseconds polling_delay = kUeventPollTimeoutMs;
+ if (!sensors[i]["PollingDelay"].empty()) {
+ const auto value = getIntFromValue(sensors[i]["PollingDelay"]);
+ polling_delay = (value > 0) ? std::chrono::milliseconds(value)
+ : std::chrono::milliseconds::max();
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s Polling delay: " << polling_delay.count();
+
+ std::chrono::milliseconds passive_delay = kMinPollIntervalMs;
+ if (!sensors[i]["PassiveDelay"].empty()) {
+ const auto value = getIntFromValue(sensors[i]["PassiveDelay"]);
+ passive_delay = (value > 0) ? std::chrono::milliseconds(value)
+ : std::chrono::milliseconds::max();
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s Passive delay: " << passive_delay.count();
+
+ std::chrono::milliseconds time_resolution;
+ if (sensors[i]["TimeResolution"].empty()) {
+ time_resolution = kMinPollIntervalMs;
+ } else {
+ time_resolution =
+ std::chrono::milliseconds(getIntFromValue(sensors[i]["TimeResolution"]));
+ }
+ LOG(INFO) << "Sensor[" << name << "]'s Time resolution: " << time_resolution.count();
+
+ if (is_hidden && send_cb) {
+ LOG(ERROR) << "is_hidden and send_cb cannot be enabled together";
+ sensors_parsed->clear();
+ return false;
+ }
+
+ std::unique_ptr<VirtualSensorInfo> virtual_sensor_info;
+ if (!ParseVirtualSensorInfo(name, sensors[i], &virtual_sensor_info)) {
+ LOG(ERROR) << "Sensor[" << name << "]: failed to parse virtual sensor info";
+ sensors_parsed->clear();
+ return false;
+ }
+
+ bool support_throttling = false; // support pid or hard limit
+ std::shared_ptr<ThrottlingInfo> throttling_info;
+ if (!ParseSensorThrottlingInfo(name, sensors[i], &support_throttling, &throttling_info)) {
+ LOG(ERROR) << "Sensor[" << name << "]: failed to parse throttling info";
+ sensors_parsed->clear();
+ return false;
+ }
+
+ bool is_watch = (send_cb | send_powerhint | support_throttling);
+ LOG(INFO) << "Sensor[" << name << "]'s is_watch: " << std::boolalpha << is_watch;
+
+ (*sensors_parsed)[name] = {
+ .type = sensor_type,
+ .hot_thresholds = hot_thresholds,
+ .cold_thresholds = cold_thresholds,
+ .hot_hysteresis = hot_hysteresis,
+ .cold_hysteresis = cold_hysteresis,
+ .temp_path = temp_path,
+ .vr_threshold = vr_threshold,
+ .multiplier = multiplier,
+ .polling_delay = polling_delay,
+ .passive_delay = passive_delay,
+ .time_resolution = time_resolution,
+ .send_cb = send_cb,
+ .send_powerhint = send_powerhint,
+ .is_watch = is_watch,
+ .is_hidden = is_hidden,
+ .virtual_sensor_info = std::move(virtual_sensor_info),
+ .throttling_info = std::move(throttling_info),
+ };
+
+ ++total_parsed;
+ }
+ LOG(INFO) << total_parsed << " Sensors parsed successfully";
+ return true;
+}
+
+bool ParseCoolingDevice(const Json::Value &config,
+ std::unordered_map<std::string, CdevInfo> *cooling_devices_parsed) {
+ Json::Value cooling_devices = config["CoolingDevices"];
+ std::size_t total_parsed = 0;
+ std::unordered_set<std::string> cooling_devices_name_parsed;
+
+ for (Json::Value::ArrayIndex i = 0; i < cooling_devices.size(); ++i) {
+ const std::string &name = cooling_devices[i]["Name"].asString();
+ LOG(INFO) << "CoolingDevice[" << i << "]'s Name: " << name;
+ if (name.empty()) {
+ LOG(ERROR) << "Failed to read CoolingDevice[" << i << "]'s Name";
+ cooling_devices_parsed->clear();
+ return false;
+ }
+
+ auto result = cooling_devices_name_parsed.insert(name.data());
+ if (!result.second) {
+ LOG(ERROR) << "Duplicate CoolingDevice[" << i << "]'s Name";
+ cooling_devices_parsed->clear();
+ return false;
+ }
+
+ std::string cooling_device_type_str = cooling_devices[i]["Type"].asString();
+ LOG(INFO) << "CoolingDevice[" << name << "]'s Type: " << cooling_device_type_str;
+ CoolingType cooling_device_type;
+
+ if (!getTypeFromString(cooling_device_type_str, &cooling_device_type)) {
+ LOG(ERROR) << "Invalid CoolingDevice[" << name
+ << "]'s Type: " << cooling_device_type_str;
+ cooling_devices_parsed->clear();
+ return false;
+ }
+
+ const std::string &read_path = cooling_devices[i]["ReadPath"].asString();
+ LOG(INFO) << "Cdev Read Path: " << (read_path.empty() ? "default" : read_path);
+
+ const std::string &write_path = cooling_devices[i]["WritePath"].asString();
+ LOG(INFO) << "Cdev Write Path: " << (write_path.empty() ? "default" : write_path);
+
+ std::vector<float> state2power;
+ Json::Value values = cooling_devices[i]["State2Power"];
+ if (values.size()) {
+ state2power.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ state2power.emplace_back(getFloatFromValue(values[j]));
+ LOG(INFO) << "Cooling device[" << name << "]'s Power2State[" << j
+ << "]: " << state2power[j];
+ }
+ } else {
+ LOG(INFO) << "CoolingDevice[" << i << "]'s Name: " << name
+ << " does not support State2Power";
+ }
+
+ const std::string &power_rail = cooling_devices[i]["PowerRail"].asString();
+ LOG(INFO) << "Cooling device power rail : " << power_rail;
+
+ (*cooling_devices_parsed)[name] = {
+ .type = cooling_device_type,
+ .read_path = read_path,
+ .write_path = write_path,
+ .state2power = state2power,
+ };
+ ++total_parsed;
+ }
+ LOG(INFO) << total_parsed << " CoolingDevices parsed successfully";
+ return true;
+}
+
+bool ParsePowerRailInfo(const Json::Value &config,
+ std::unordered_map<std::string, PowerRailInfo> *power_rails_parsed) {
+ Json::Value power_rails = config["PowerRails"];
+ std::size_t total_parsed = 0;
+ std::unordered_set<std::string> power_rails_name_parsed;
+
+ for (Json::Value::ArrayIndex i = 0; i < power_rails.size(); ++i) {
+ const std::string &name = power_rails[i]["Name"].asString();
+ LOG(INFO) << "PowerRail[" << i << "]'s Name: " << name;
+ if (name.empty()) {
+ LOG(ERROR) << "Failed to read PowerRail[" << i << "]'s Name";
+ power_rails_parsed->clear();
+ return false;
+ }
+
+ std::string rail;
+ if (power_rails[i]["Rail"].empty()) {
+ rail = name;
+ } else {
+ rail = power_rails[i]["Rail"].asString();
+ }
+ LOG(INFO) << "PowerRail[" << i << "]'s Rail: " << rail;
+
+ std::vector<std::string> linked_power_rails;
+ std::vector<float> coefficients;
+ float offset = 0;
+ FormulaOption formula = FormulaOption::COUNT_THRESHOLD;
+ bool is_virtual_power_rail = false;
+ Json::Value values;
+ int power_sample_count = 0;
+ std::chrono::milliseconds power_sample_delay;
+
+ if (!power_rails[i]["VirtualRails"].empty() && power_rails[i]["VirtualRails"].isBool()) {
+ is_virtual_power_rail = power_rails[i]["VirtualRails"].asBool();
+ LOG(INFO) << "PowerRails[" << name << "]'s VirtualRail, set to 'true'";
+ }
+
+ if (is_virtual_power_rail) {
+ values = power_rails[i]["Combination"];
+ if (values.size()) {
+ linked_power_rails.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ linked_power_rails.emplace_back(values[j].asString());
+ LOG(INFO) << "PowerRail[" << name << "]'s combination[" << j
+ << "]: " << linked_power_rails[j];
+ }
+ } else {
+ LOG(ERROR) << "PowerRails[" << name << "] has no combination for VirtualRail";
+ power_rails_parsed->clear();
+ return false;
+ }
+
+ values = power_rails[i]["Coefficient"];
+ if (values.size()) {
+ coefficients.reserve(values.size());
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ coefficients.emplace_back(getFloatFromValue(values[j]));
+ LOG(INFO) << "PowerRail[" << name << "]'s coefficient[" << j
+ << "]: " << coefficients[j];
+ }
+ } else {
+ LOG(ERROR) << "PowerRails[" << name << "] has no coefficient for VirtualRail";
+ power_rails_parsed->clear();
+ return false;
+ }
+
+ if (linked_power_rails.size() != coefficients.size()) {
+ LOG(ERROR) << "PowerRails[" << name
+ << "]'s combination size is not matched with coefficient size";
+ power_rails_parsed->clear();
+ return false;
+ }
+
+ if (!power_rails[i]["Offset"].empty()) {
+ offset = power_rails[i]["Offset"].asFloat();
+ }
+
+ if (linked_power_rails.size() != coefficients.size()) {
+ LOG(ERROR) << "PowerRails[" << name
+ << "]'s combination size is not matched with coefficient size";
+ power_rails_parsed->clear();
+ return false;
+ }
+
+ if (power_rails[i]["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
+ formula = FormulaOption::COUNT_THRESHOLD;
+ } else if (power_rails[i]["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
+ formula = FormulaOption::WEIGHTED_AVG;
+ } else if (power_rails[i]["Formula"].asString().compare("MAXIMUM") == 0) {
+ formula = FormulaOption::MAXIMUM;
+ } else if (power_rails[i]["Formula"].asString().compare("MINIMUM") == 0) {
+ formula = FormulaOption::MINIMUM;
+ } else {
+ LOG(ERROR) << "PowerRails[" << name << "]'s Formula is invalid";
+ power_rails_parsed->clear();
+ return false;
+ }
+ }
+
+ std::unique_ptr<VirtualPowerRailInfo> virtual_power_rail_info;
+ if (is_virtual_power_rail) {
+ virtual_power_rail_info.reset(
+ new VirtualPowerRailInfo{linked_power_rails, coefficients, offset, formula});
+ }
+
+ power_sample_count = power_rails[i]["PowerSampleCount"].asInt();
+ LOG(INFO) << "Power sample Count: " << power_sample_count;
+
+ if (!power_rails[i]["PowerSampleDelay"]) {
+ power_sample_delay = std::chrono::milliseconds::max();
+ } else {
+ power_sample_delay =
+ std::chrono::milliseconds(getIntFromValue(power_rails[i]["PowerSampleDelay"]));
+ }
+
+ (*power_rails_parsed)[name] = {
+ .rail = rail,
+ .power_sample_count = power_sample_count,
+ .power_sample_delay = power_sample_delay,
+ .virtual_power_rail_info = std::move(virtual_power_rail_info),
+ };
+ ++total_parsed;
+ }
+ LOG(INFO) << total_parsed << " PowerRails parsed successfully";
+ return true;
+}
+
+template <typename T, typename U>
+bool ParseStatsInfo(const Json::Value &stats_config,
+ const std::unordered_map<std::string, U> &entity_info, StatsInfo<T> *stats_info,
+ T min_value) {
+ if (stats_config.empty()) {
+ LOG(INFO) << "No stats config";
+ return true;
+ }
+ std::variant<bool, std::unordered_set<std::string>>
+ record_by_default_threshold_all_or_name_set_ = false;
+ if (stats_config["DefaultThresholdEnableAll"].empty() ||
+ !stats_config["DefaultThresholdEnableAll"].isBool()) {
+ LOG(INFO) << "Failed to read stats DefaultThresholdEnableAll, set to 'false'";
+ } else if (stats_config["DefaultThresholdEnableAll"].asBool()) {
+ record_by_default_threshold_all_or_name_set_ = true;
+ }
+ LOG(INFO) << "DefaultThresholdEnableAll " << std::boolalpha
+ << std::get<bool>(record_by_default_threshold_all_or_name_set_) << std::noboolalpha;
+
+ Json::Value values = stats_config["RecordWithDefaultThreshold"];
+ if (values.size()) {
+ if (std::get<bool>(record_by_default_threshold_all_or_name_set_)) {
+ LOG(ERROR) << "Cannot enable record with default threshold when "
+ "DefaultThresholdEnableAll true.";
+ return false;
+ }
+ record_by_default_threshold_all_or_name_set_ = std::unordered_set<std::string>();
+ for (Json::Value::ArrayIndex i = 0; i < values.size(); ++i) {
+ std::string name = values[i].asString();
+ if (!entity_info.count(name)) {
+ LOG(ERROR) << "Unknown name [" << name << "] not present in entity_info.";
+ return false;
+ }
+ std::get<std::unordered_set<std::string>>(record_by_default_threshold_all_or_name_set_)
+ .insert(name);
+ }
+ } else {
+ LOG(INFO) << "No stat by default threshold enabled.";
+ }
+
+ std::unordered_map<std::string, std::vector<ThresholdList<T>>> record_by_threshold;
+ values = stats_config["RecordWithThreshold"];
+ if (values.size()) {
+ Json::Value threshold_values;
+ for (Json::Value::ArrayIndex i = 0; i < values.size(); i++) {
+ const std::string &name = values[i]["Name"].asString();
+ if (!entity_info.count(name)) {
+ LOG(ERROR) << "Unknown name [" << name << "] not present in entity_info.";
+ return false;
+ }
+
+ std::optional<std::string> logging_name;
+ if (!values[i]["LoggingName"].empty()) {
+ logging_name = values[i]["LoggingName"].asString();
+ LOG(INFO) << "For [" << name << "]"
+ << ", stats logging name is [" << logging_name.value() << "]";
+ }
+
+ LOG(INFO) << "Start to parse stats threshold for [" << name << "]";
+ threshold_values = values[i]["Thresholds"];
+ if (threshold_values.empty()) {
+ LOG(ERROR) << "Empty stats threshold not valid.";
+ return false;
+ }
+ const auto &threshold_values_count = threshold_values.size();
+ if (threshold_values_count > kMaxStatsThresholdCount) {
+ LOG(ERROR) << "Number of stats threshold " << threshold_values_count
+ << " greater than max " << kMaxStatsThresholdCount;
+ return false;
+ }
+ std::vector<T> stats_threshold(threshold_values_count);
+ T prev_value = min_value;
+ LOG(INFO) << "Thresholds:";
+ for (Json::Value::ArrayIndex i = 0; i < threshold_values_count; ++i) {
+ stats_threshold[i] = std::is_floating_point_v<T>
+ ? getFloatFromValue(threshold_values[i])
+ : getIntFromValue(threshold_values[i]);
+ if (stats_threshold[i] <= prev_value) {
+ LOG(ERROR) << "Invalid array[" << i << "]" << stats_threshold[i]
+ << " is <=" << prev_value;
+ return false;
+ }
+ prev_value = stats_threshold[i];
+ LOG(INFO) << "[" << i << "]: " << stats_threshold[i];
+ }
+ record_by_threshold[name].emplace_back(logging_name, stats_threshold);
+ }
+ } else {
+ LOG(INFO) << "No stat by threshold enabled.";
+ }
+
+ (*stats_info) = {.record_by_default_threshold_all_or_name_set_ =
+ record_by_default_threshold_all_or_name_set_,
+ .record_by_threshold = record_by_threshold};
+ return true;
+}
+
+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_parsed) {
+ Json::Value stats_config = config["Stats"];
+
+ if (stats_config.empty()) {
+ LOG(INFO) << "No Stats Config present.";
+ return true;
+ }
+
+ LOG(INFO) << "Parse Stats Config for Sensor Temp.";
+ // Parse sensor stats config
+ if (!ParseStatsInfo(stats_config["Sensors"], sensor_info_map_,
+ &stats_config_parsed->sensor_stats_info,
+ std::numeric_limits<float>::lowest())) {
+ LOG(ERROR) << "Failed to parse sensor temp stats info.";
+ stats_config_parsed->clear();
+ return false;
+ }
+
+ // Parse cooling device user vote
+ if (stats_config["CoolingDevices"].empty()) {
+ LOG(INFO) << "No cooling device stats present.";
+ return true;
+ }
+
+ LOG(INFO) << "Parse Stats Config for Sensor CDev Request.";
+ if (!ParseStatsInfo(stats_config["CoolingDevices"]["RecordVotePerSensor"],
+ cooling_device_info_map_, &stats_config_parsed->cooling_device_request_info,
+ -1)) {
+ LOG(ERROR) << "Failed to parse cooling device user vote stats info.";
+ stats_config_parsed->clear();
+ return false;
+ }
+ return true;
+}
+
+} // namespace implementation
+} // namespace thermal
+} // namespace hardware
+} // namespace android
+} // namespace aidl
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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