From: Tim Zimmermann Date: Sun, 12 Nov 2023 05:43:40 +0000 (+0100) Subject: aidl: Import Pixel Thermal HAL aidl implementation X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=96d890751484a60233467b284bcdee43809464b5;p=GitHub%2FLineageOS%2Fandroid_hardware_samsung.git aidl: Import Pixel Thermal HAL aidl implementation * From hardware/google/pixel @ android-14.0.0_r15 Change-Id: I30e35c8e4ef58956f849d64e184aa7e37ec67ef9 --- diff --git a/aidl/thermal/Android.bp b/aidl/thermal/Android.bp new file mode 100644 index 0000000..5f426f9 --- /dev/null +++ b/aidl/thermal/Android.bp @@ -0,0 +1,78 @@ +cc_binary { + name: "android.hardware.thermal-service.pixel", + srcs: [ + "service.cpp", + "Thermal.cpp", + "thermal-helper.cpp", + "utils/thermal_throttling.cpp", + "utils/thermal_info.cpp", + "utils/thermal_files.cpp", + "utils/power_files.cpp", + "utils/powerhal_helper.cpp", + "utils/thermal_stats_helper.cpp", + "utils/thermal_watcher.cpp", + ], + vendor: true, + relative_install_path: "hw", + vintf_fragments: [ + "android.hardware.thermal-service.pixel.xml" + ], + init_rc: [ + "android.hardware.thermal-service.pixel.rc", + ], + shared_libs: [ + "libbase", + "libcutils", + "libjsoncpp", + "libutils", + "libnl", + "libbinder_ndk", + "android.frameworks.stats-V1-ndk", + "android.hardware.power-V1-ndk", + "android.hardware.thermal-V1-ndk", + "pixel-power-ext-V1-ndk", + "pixelatoms-cpp", + ], + static_libs: [ + "libpixelstats", + ], + export_shared_lib_headers: [ + "android.frameworks.stats-V1-ndk", + "pixelatoms-cpp", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + "-Wunused", + ], + tidy: true, + tidy_checks: [ + "android-*", + "cert-*", + "clang-analyzer-security*", + ], + tidy_checks_as_errors: [ + "android-*", + "clang-analyzer-security*", + "cert-*", + ], +} + +sh_binary { + name: "thermal_logd", + src: "init.thermal.logging.sh", + vendor: true, + init_rc: [ + "pixel-thermal-logd.rc", + ], +} + +sh_binary { + name: "thermal_symlinks", + src: "init.thermal.symlinks.sh", + vendor: true, + init_rc: [ + "pixel-thermal-symlinks.rc", + ], +} diff --git a/aidl/thermal/Thermal.cpp b/aidl/thermal/Thermal.cpp new file mode 100644 index 0000000..776341d --- /dev/null +++ b/aidl/thermal/Thermal.cpp @@ -0,0 +1,793 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL) + +#include "Thermal.h" + +#include +#include +#include + +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 *_aidl_return) { + return getFilteredTemperatures(false, TemperatureType::UNKNOWN, _aidl_return); +} + +ndk::ScopedAStatus Thermal::getTemperaturesWithType(TemperatureType type, + std::vector *_aidl_return) { + return getFilteredTemperatures(true, type, _aidl_return); +} + +ndk::ScopedAStatus Thermal::getFilteredTemperatures(bool filterType, TemperatureType type, + std::vector *_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 *_aidl_return) { + return getFilteredCoolingDevices(false, CoolingType::BATTERY, _aidl_return); +} + +ndk::ScopedAStatus Thermal::getCoolingDevicesWithType(CoolingType type, + std::vector *_aidl_return) { + return getFilteredCoolingDevices(true, type, _aidl_return); +} + +ndk::ScopedAStatus Thermal::getFilteredCoolingDevices(bool filterType, CoolingType type, + std::vector *_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 *_aidl_return) { + *_aidl_return = {}; + return getFilteredTemperatureThresholds(false, TemperatureType::UNKNOWN, _aidl_return); +} + +ndk::ScopedAStatus Thermal::getTemperatureThresholdsWithType( + TemperatureType type, std::vector *_aidl_return) { + return getFilteredTemperatureThresholds(true, type, _aidl_return); +} + +ndk::ScopedAStatus Thermal::getFilteredTemperatureThresholds( + bool filterType, TemperatureType type, std::vector *_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 &callback) { + ATRACE_CALL(); + return registerThermalChangedCallback(callback, false, TemperatureType::UNKNOWN); +} + +ndk::ScopedAStatus Thermal::registerThermalChangedCallbackWithType( + const std::shared_ptr &callback, TemperatureType type) { + ATRACE_CALL(); + return registerThermalChangedCallback(callback, true, type); +} + +ndk::ScopedAStatus Thermal::unregisterThermalChangedCallback( + const std::shared_ptr &callback) { + if (callback == nullptr) { + return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT, + "Invalid nullptr callback"); + } + bool removed = false; + std::lock_guard _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 &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 _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 handler = [this, c, filterType, type]() { + std::vector 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 _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( + 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( + 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 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 lock(mutex_); + events_.push(e); + cv_.notify_all(); +} + +void Thermal::Looper::loop() { + while (true) { + std::unique_lock 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 diff --git a/aidl/thermal/Thermal.h b/aidl/thermal/Thermal.h new file mode 100644 index 0000000..74449c4 --- /dev/null +++ b/aidl/thermal/Thermal.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include + +#include "thermal-helper.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace thermal { +namespace implementation { + +struct CallbackSetting { + CallbackSetting(std::shared_ptr callback, bool is_filter_type, + TemperatureType type) + : callback(std::move(callback)), is_filter_type(is_filter_type), type(type) {} + std::shared_ptr callback; + bool is_filter_type; + TemperatureType type; +}; + +class Thermal : public BnThermal { + public: + Thermal(); + ~Thermal() = default; + ndk::ScopedAStatus getTemperatures(std::vector *_aidl_return) override; + ndk::ScopedAStatus getTemperaturesWithType(TemperatureType type, + std::vector *_aidl_return) override; + + ndk::ScopedAStatus getCoolingDevices(std::vector *_aidl_return) override; + ndk::ScopedAStatus getCoolingDevicesWithType(CoolingType type, + std::vector *_aidl_return) override; + + ndk::ScopedAStatus getTemperatureThresholds( + std::vector *_aidl_return) override; + ndk::ScopedAStatus getTemperatureThresholdsWithType( + TemperatureType type, std::vector *_aidl_return) override; + + ndk::ScopedAStatus registerThermalChangedCallback( + const std::shared_ptr &callback) override; + ndk::ScopedAStatus registerThermalChangedCallbackWithType( + const std::shared_ptr &callback, + TemperatureType type) override; + ndk::ScopedAStatus unregisterThermalChangedCallback( + const std::shared_ptr &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 handler; + }; + + Looper() { + thread_ = std::thread([&] { loop(); }); + } + void addEvent(const Event &e); + + private: + std::condition_variable cv_; + std::queue events_; + std::mutex mutex_; + std::thread thread_; + + void loop(); + }; + + ThermalHelper thermal_helper_; + std::mutex thermal_callback_mutex_; + std::vector callbacks_; + Looper looper_; + + ndk::ScopedAStatus getFilteredTemperatures(bool filterType, TemperatureType type, + std::vector *_aidl_return); + ndk::ScopedAStatus getFilteredCoolingDevices(bool filterType, CoolingType type, + std::vector *_aidl_return); + ndk::ScopedAStatus getFilteredTemperatureThresholds( + bool filterType, TemperatureType type, std::vector *_aidl_return); + ndk::ScopedAStatus registerThermalChangedCallback( + const std::shared_ptr &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 diff --git a/aidl/thermal/android.hardware.thermal-service.pixel.rc b/aidl/thermal/android.hardware.thermal-service.pixel.rc new file mode 100644 index 0000000..f9f823b --- /dev/null +++ b/aidl/thermal/android.hardware.thermal-service.pixel.rc @@ -0,0 +1,14 @@ +on property:vendor.thermal.link_ready=1 + # queue the trigger to start thermal-hal and continue execute + # per-device thermal setup "on property:vendor.thermal.link_ready=1" + trigger enable-thermal-hal + +on enable-thermal-hal + restart vendor.thermal-hal + +service vendor.thermal-hal /vendor/bin/hw/android.hardware.thermal-service.pixel + class hal + user system + group system + priority -20 + disabled diff --git a/aidl/thermal/android.hardware.thermal-service.pixel.xml b/aidl/thermal/android.hardware.thermal-service.pixel.xml new file mode 100644 index 0000000..bdee744 --- /dev/null +++ b/aidl/thermal/android.hardware.thermal-service.pixel.xml @@ -0,0 +1,7 @@ + + + android.hardware.thermal + 1 + IThermal/default + + diff --git a/aidl/thermal/init.thermal.logging.sh b/aidl/thermal/init.thermal.logging.sh new file mode 100755 index 0000000..de385ab --- /dev/null +++ b/aidl/thermal/init.thermal.logging.sh @@ -0,0 +1,25 @@ +#!/vendor/bin/sh + +if [ $# -eq 1 ]; then + interval=$1 +else + exit 1 +fi + +while true +do + logline="tz:" + for f in /sys/class/thermal/thermal* + do + temp=`cat $f/temp` + logline+="|$temp" + done + logline+=" cdev:" + for f in /sys/class/thermal/cooling_device* + do + cur_state=`cat $f/cur_state` + logline+="|$cur_state" + done + log -p w -t THERMAL_LOG $logline + sleep $interval +done diff --git a/aidl/thermal/init.thermal.symlinks.sh b/aidl/thermal/init.thermal.symlinks.sh new file mode 100755 index 0000000..d990897 --- /dev/null +++ b/aidl/thermal/init.thermal.symlinks.sh @@ -0,0 +1,13 @@ +#!/vendor/bin/sh + +for f in /sys/class/thermal/thermal_zone* +do + tz_name=`cat $f/type` + ln -s $f /dev/thermal/tz-by-name/$tz_name +done +for f in /sys/class/thermal/cooling_device* +do + cdev_name=`cat $f/type` + ln -s $f /dev/thermal/cdev-by-name/$cdev_name +done +setprop vendor.thermal.link_ready 1 diff --git a/aidl/thermal/pixel-thermal-logd.rc b/aidl/thermal/pixel-thermal-logd.rc new file mode 100644 index 0000000..c2ec9ff --- /dev/null +++ b/aidl/thermal/pixel-thermal-logd.rc @@ -0,0 +1,130 @@ +on property:persist.vendor.log.thermal=1 + start vendor.thermal.logd + +on property:persist.vendor.log.thermal=0 + stop vendor.thermal.logd + +on property:persist.vendor.log.thermal=1 && property:persist.vendor.log.thermal.interval=* + restart vendor.thermal.logd + +service vendor.thermal.logd /vendor/bin/thermal_logd ${persist.vendor.log.thermal.interval:-5} + class main + user root + group root system + disabled + +# Switch thermal protection for Pixels +on property:persist.vendor.disable.thermal.control=* + setprop vendor.disable.thermal.control ${persist.vendor.disable.thermal.control} + +on property:persist.vendor.disable.thermalhal.control=* + setprop vendor.disable.thermalhal.control ${persist.vendor.disable.thermalhal.control} + +on property:persist.vendor.disable.usb.overheat.mitigation=* + setprop vendor.disable.usb.overheat.mitigation.control ${persist.vendor.disable.usb.overheat.mitigation} + +on property:persist.vendor.disable.bcl.control=* + setprop vendor.disable.bcl.control ${persist.vendor.disable.bcl.control} + +on property:vendor.disable.thermalhal.control=* && property:vendor.thermal.link_ready=1 + restart vendor.thermal-hal + +on property:vendor.disable.thermal.control=1 && property:vendor.thermal.link_ready=1 + # common + stop vendor.thermal-engine + setprop vendor.disable.thermalhal.control 1 + # sdm845 + write /dev/thermal/tz-by-name/quiet-therm-adc/mode disabled + write /dev/thermal/tz-by-name/quiet-therm-monitor/mode disabled + write /dev/thermal/tz-by-name/fps-therm-adc/mode disabled + write /dev/thermal/tz-by-name/fps-therm-monitor/mode disabled + # sdm670 + write /dev/thermal/tz-by-name/mb-therm-adc/mode disabled + write /dev/thermal/tz-by-name/mb-therm-monitor/mode disabled + # sm8150 + write /dev/thermal/tz-by-name/sdm-therm/mode disabled + write /dev/thermal/tz-by-name/sdm-therm-monitor/mode disabled + # sm7150 + write /dev/thermal/tz-by-name/skin-therm-adc/mode disabled + write /dev/thermal/tz-by-name/skin-therm-monitor/mode disabled + # sm7250 + write /dev/thermal/tz-by-name/skin-therm/emul_temp 25000 + write /dev/thermal/tz-by-name/skin-therm/mode disabled + write /dev/thermal/tz-by-name/skin-virt/emul_temp 25000 + write /dev/thermal/tz-by-name/skin-virt/mode disabled + write /dev/thermal/tz-by-name/skin-therm-cpu/emul_temp 25000 + write /dev/thermal/tz-by-name/skin-therm-cpu/mode disabled + write /dev/thermal/tz-by-name/skin-virt-cpu/emul_temp 25000 + write /dev/thermal/tz-by-name/skin-virt-cpu/mode disabled + write /dev/thermal/tz-by-name/skin-therm-monitor/emul_temp 25000 + write /dev/thermal/tz-by-name/skin-therm-monitor/mode disabled + write /dev/thermal/tz-by-name/skin-virt-monitor/emul_temp 25000 + write /dev/thermal/tz-by-name/skin-virt-monitor/mode disabled + write /dev/thermal/tz-by-name/panel-audio-therm/emul_temp 25000 + write /dev/thermal/tz-by-name/panel-audio-therm/mode disabled + write /dev/thermal/tz-by-name/cellular-emergency/emul_temp 25000 + write /dev/thermal/tz-by-name/cellular-emergency/mode disabled + write /dev/thermal/tz-by-name/sdm-therm/emul_temp 25000 + write /dev/thermal/tz-by-name/sdm-therm/mode disabled + write /dev/thermal/tz-by-name/charger-therm/emul_temp 25000 + write /dev/thermal/tz-by-name/charger-therm/mode disabled + # P21 + write /dev/thermal/tz-by-name/disp_therm/mode disabled + +on property:vendor.disable.thermal.control=0 && property:vendor.thermal.link_ready=1 + # common + start vendor.thermal-engine + setprop vendor.disable.thermalhal.control 0 + # sdm845 + write /dev/thermal/tz-by-name/quiet-therm-adc/mode enabled + write /dev/thermal/tz-by-name/quiet-therm-monitor/mode enabled + write /dev/thermal/tz-by-name/fps-therm-adc/mode enabled + write /dev/thermal/tz-by-name/fps-therm-monitor/mode enabled + # sdm670 + write /dev/thermal/tz-by-name/mb-therm-adc/mode enabled + write /dev/thermal/tz-by-name/mb-therm-monitor/mode enabled + # sm8150 + write /dev/thermal/tz-by-name/sdm-therm/mode enabled + write /dev/thermal/tz-by-name/sdm-therm-monitor/mode enabled + # sm7150 + write /dev/thermal/tz-by-name/skin-therm-adc/mode enabled + write /dev/thermal/tz-by-name/skin-therm-monitor/mode enabled + # sm7250 + write /dev/thermal/tz-by-name/skin-therm/emul_temp 0 + write /dev/thermal/tz-by-name/skin-therm/mode enabled + write /dev/thermal/tz-by-name/skin-virt/emul_temp 0 + write /dev/thermal/tz-by-name/skin-virt/mode enabled + write /dev/thermal/tz-by-name/skin-therm-cpu/emul_temp 0 + write /dev/thermal/tz-by-name/skin-therm-cpu/mode enabled + write /dev/thermal/tz-by-name/skin-virt-cpu/emul_temp 0 + write /dev/thermal/tz-by-name/skin-virt-cpu/mode enabled + write /dev/thermal/tz-by-name/skin-therm-monitor/emul_temp 0 + write /dev/thermal/tz-by-name/skin-therm-monitor/mode enabled + write /dev/thermal/tz-by-name/skin-virt-monitor/emul_temp 0 + write /dev/thermal/tz-by-name/skin-virt-monitor/mode enabled + write /dev/thermal/tz-by-name/panel-audio-therm/emul_temp 0 + write /dev/thermal/tz-by-name/panel-audio-therm/mode enabled + write /dev/thermal/tz-by-name/cellular-emergency/emul_temp 0 + write /dev/thermal/tz-by-name/cellular-emergency/mode enabled + write /dev/thermal/tz-by-name/sdm-therm/emul_temp 0 + write /dev/thermal/tz-by-name/sdm-therm/mode enabled + write /dev/thermal/tz-by-name/charger-therm/emul_temp 0 + write /dev/thermal/tz-by-name/charger-therm/mode enabled + # P21 + write /dev/thermal/tz-by-name/disp_therm/mode enabled + +# Toggle BCL control +on property:vendor.disable.bcl.control=1 + write /dev/thermal/tz-by-name/soc/mode disabled + +on property:vendor.disable.bcl.control=0 + write /dev/thermal/tz-by-name/soc/mode enabled + +# Switch USB port overheat protection +on property:vendor.disable.usb.overheat.mitigation.control=1 + write /sys/module/overheat_mitigation/parameters/enable 0 + write /dev/thermal/tz-by-name/usb_pwr_therm2/emul_temp 25000 + +on property:vendor.disable.usb.overheat.mitigation.control=0 + write /sys/module/overheat_mitigation/parameters/enable 1 + write /dev/thermal/tz-by-name/usb_pwr_therm2/emul_temp 0 diff --git a/aidl/thermal/pixel-thermal-symlinks.rc b/aidl/thermal/pixel-thermal-symlinks.rc new file mode 100644 index 0000000..132ec5f --- /dev/null +++ b/aidl/thermal/pixel-thermal-symlinks.rc @@ -0,0 +1,11 @@ +on boot + mkdir /dev/thermal 0750 system system + mkdir /dev/thermal/tz-by-name 0750 system system + mkdir /dev/thermal/cdev-by-name 0750 system system + start vendor.thermal.symlinks + +service vendor.thermal.symlinks /vendor/bin/thermal_symlinks + user system + group system + oneshot + disabled diff --git a/aidl/thermal/service.cpp b/aidl/thermal/service.cpp new file mode 100644 index 0000000..d18a069 --- /dev/null +++ b/aidl/thermal/service.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include + +#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(); + 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 +} diff --git a/aidl/thermal/thermal-helper.cpp b/aidl/thermal/thermal-helper.cpp new file mode 100644 index 0000000..d7411bc --- /dev/null +++ b/aidl/thermal/thermal-helper.cpp @@ -0,0 +1,1180 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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 parseThermalPathMap(std::string_view prefix) { + std::unordered_map path_map; + std::unique_ptr 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::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 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 _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 _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(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 _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 *throttling_status, + const bool force_no_cache) { + // Return fail if the thermal sensor cannot be read. + float temp; + std::map 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 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 _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 _lock(sensor_status_map_mutex_); + out->throttlingStatus = + static_cast(sensor_status.emul_setting->emul_severity); + } else { + out->throttlingStatus = + static_cast(status.first) > static_cast(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 &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 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(ThrottlingSeverity::SHUTDOWN); + i > static_cast(ThrottlingSeverity::NONE); --i) { + if (!std::isnan(hot_thresholds[i]) && hot_thresholds[i] <= value && + ret_hot == ThrottlingSeverity::NONE) { + ret_hot = static_cast(i); + } + if (!std::isnan(hot_thresholds[i]) && (hot_thresholds[i] - hot_hysteresis[i]) < value && + ret_hot_hysteresis == ThrottlingSeverity::NONE) { + ret_hot_hysteresis = static_cast(i); + } + if (!std::isnan(cold_thresholds[i]) && cold_thresholds[i] >= value && + ret_cold == ThrottlingSeverity::NONE) { + ret_cold = static_cast(i); + } + if (!std::isnan(cold_thresholds[i]) && (cold_thresholds[i] + cold_hysteresis[i]) > value && + ret_cold_hysteresis == ThrottlingSeverity::NONE) { + ret_cold_hysteresis = static_cast(i); + } + } + if (static_cast(ret_hot) < static_cast(prev_hot_severity)) { + ret_hot = ret_hot_hysteresis; + } + if (static_cast(ret_cold) < static_cast(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()) { + power_hal_service_.setMode(sensor_info_pair.first, severity, false); + } + } + } +} + +bool ThermalHelper::initializeSensorMap( + const std::unordered_map &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 &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(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::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(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 &path_map, + std::set *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( + 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( + 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 *temperatures) { + std::vector 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 *thresholds) const { + std::vector 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 *cooling_devices) const { + std::vector 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 *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 *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 _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( + 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(*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::lowest(); + if (sensor_reading * coefficient > temp_val) + temp_val = sensor_reading * coefficient; + break; + case FormulaOption::MINIMUM: + if (i == 0) + temp_val = std::numeric_limits::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(*temp)); + + { + std::unique_lock _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 &uevent_sensors) { + std::vector temps; + std::vector 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( + 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 _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 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 _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()) { + 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 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 diff --git a/aidl/thermal/thermal-helper.h b/aidl/thermal/thermal-helper.h new file mode 100644 index 0000000..3b790e3 --- /dev/null +++ b/aidl/thermal/thermal-helper.h @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + +// 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 emul_setting; +}; + +class ThermalHelper { + public: + explicit ThermalHelper(const NotificationCallback &cb); + ~ThermalHelper() = default; + + bool fillCurrentTemperatures(bool filterType, bool filterCallback, TemperatureType type, + std::vector *temperatures); + bool fillTemperatureThresholds(bool filterType, TemperatureType type, + std::vector *thresholds) const; + bool fillCurrentCoolingDevices(bool filterType, CoolingType type, + std::vector *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 *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 &GetSensorInfoMap() const { + return sensor_info_map_; + } + // Get CdevInfo Map + const std::unordered_map &GetCdevInfoMap() const { + return cooling_device_info_map_; + } + // Get SensorStatus Map + const std::unordered_map &GetSensorStatusMap() const { + std::shared_lock _lock(sensor_status_map_mutex_); + return sensor_status_map_; + } + // Get ThermalThrottling Map + const std::unordered_map &GetThermalThrottlingStatusMap() + const { + return thermal_throttling_.GetThermalThrottlingStatusMap(); + } + // Get PowerRailInfo Map + const std::unordered_map &GetPowerRailInfoMap() const { + return power_files_.GetPowerRailInfoMap(); + } + + // Get PowerStatus Map + const std::unordered_map &GetPowerStatusMap() const { + return power_files_.GetPowerStatusMap(); + } + + // Get Thermal Stats Sensor Map + const std::unordered_map GetSensorTempStatsSnapshot() { + return thermal_stats_helper_.GetSensorTempStatsSnapshot(); + } + // Get Thermal Stats Sensor, Binded Cdev State Request Map + const std::unordered_map>> + 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 &path_map); + bool initializeCoolingDevices(const std::unordered_map &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 &path_map, + std::set *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 &uevent_sensors); + // Return hot and cold severity status as std::pair + std::pair 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 *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 *sensor_log_map); + bool connectToPowerHal(); + void updateSupportedPowerHints(); + void updateCoolingDevices(const std::vector &cooling_devices_to_update); + sp thermal_watcher_; + PowerFiles power_files_; + ThermalFiles thermal_sensors_; + ThermalFiles cooling_devices_; + ThermalThrottling thermal_throttling_; + bool is_initialized_; + const NotificationCallback cb_; + std::unordered_map cooling_device_info_map_; + std::unordered_map sensor_info_map_; + std::unordered_map> + supported_powerhint_map_; + PowerHalService power_hal_service_; + ThermalStatsHelper thermal_stats_helper_; + mutable std::shared_mutex sensor_status_map_mutex_; + std::unordered_map sensor_status_map_; +}; + +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/config_schema.json b/aidl/thermal/utils/config_schema.json new file mode 100644 index 0000000..fd493e1 --- /dev/null +++ b/aidl/thermal/utils/config_schema.json @@ -0,0 +1,219 @@ +{ + "definitions":{ + + }, + "$schema":"http://json-schema.org/draft-07/schema#", + "$id":"http://example.com/root.json", + "type":"object", + "title":"The Root Schema", + "required":[ + "Sensors" + ], + "properties":{ + "Sensors":{ + "$id":"#/properties/Sensors", + "type":"array", + "title":"The Sensors Schema", + "items":{ + "$id":"#/properties/Sensors/items", + "type":"object", + "title":"The Items Schema", + "required":[ + "Name", + "Type", + "HotThreshold", + "VrThreshold", + "Multiplier" + ], + "properties":{ + "Name":{ + "$id":"#/properties/Sensors/items/properties/Name", + "type":"string", + "title":"The Name Schema", + "default":"", + "examples":[ + "cpu0-silver-usr" + ], + "pattern":"^(.+)$" + }, + "Type":{ + "$id":"#/properties/Sensors/items/properties/Type", + "type":"string", + "title":"The Type Schema", + "default":"", + "examples":[ + "CPU" + ], + "pattern":"^(.+)$" + }, + "HotThreshold":{ + "$id":"#/properties/Sensors/items/properties/HotThreshold", + "type":"array", + "title":"The hot threshold Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN", + "default":"NAN", + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/HotThreshold/items", + "type":[ + "string", + "number" + ], + "title":"The Items Schema", + "default":"", + "examples":[ + "NAN", + "NAN", + "NAN", + 95, + "NAN", + "NAN", + 125 + ], + "pattern":"^([-+]?[0-9]*\\.?[0-9]+|NAN)$" + } + }, + "HotHysteresis":{ + "$id":"#/properties/Sensors/items/properties/HotHysteresis", + "type":"array", + "title":"The hot hysteresis Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN. Throttling status will be cleared HotThreshold - HotHysteresis.", + "default":null, + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/HotHysteresis/items", + "type":[ + "number" + ], + "title":"The Items Schema", + "default":0.0, + "examples":[ + 0.0, + 0.0, + 0.0, + 1.0, + 1.5, + 1.0, + 2.0 + ] + } + }, + "ColdThreshold":{ + "$id":"#/properties/Sensors/items/properties/ColdThreshold", + "type":"array", + "title":"The cold threshold Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN, default to NAN", + "default":null, + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/ColdThreshold/items", + "type":"string", + "title":"The Items Schema", + "default":"NAN", + "examples":[ + "NAN", + "NAN", + "NAN", + "NAN", + "NAN", + "NAN", + "NAN" + ], + "pattern":"^([-+]?[0-9]*\\.?[0-9]+|NAN)$" + } + }, + "ColdHysteresis":{ + "$id":"#/properties/Sensors/items/properties/ColdHysteresis", + "type":"array", + "title":"The cold hysteresis Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN. Throttling status will be cleared ColdThreshold + ColdHysteresis.", + "default":null, + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/ColdHysteresis/items", + "type":[ + "number" + ], + "title":"The Items Schema", + "default":0.0, + "examples":[ + 0.0, + 0.0, + 0.0, + 1.0, + 1.5, + 1.0, + 2.0 + ] + } + }, + "VrThreshold":{ + "$id":"#/properties/Sensors/items/properties/VrThreshold", + "type":"string", + "title":"The Vrthreshold Schema", + "default":"", + "examples":[ + "NAN" + ], + "pattern":"^(.*)$" + }, + "Multiplier":{ + "$id":"#/properties/Sensors/items/properties/Multiplier", + "type":"number", + "title":"The Multiplier Schema", + "default":0.001, + "examples":[ + 0.001 + ], + "exclusiveMinimum":0.0 + }, + "Monitor":{ + "$id":"#/properties/Sensors/items/properties/Monitor", + "type":"boolean", + "title":"The Monitor Schema, if the sensor will be monitored and used to trigger throttling event", + "default":false, + "examples":[ + true + ] + } + } + } + }, + "CoolingDevices":{ + "$id":"#/properties/CoolingDevices", + "type":"array", + "title":"The Coolingdevices Schema", + "items":{ + "$id":"#/properties/CoolingDevices/items", + "type":"object", + "title":"The Items Schema", + "required":[ + "Name", + "Type" + ], + "properties":{ + "Name":{ + "$id":"#/properties/CoolingDevices/items/properties/Name", + "type":"string", + "title":"The Name Schema", + "default":"", + "examples":[ + "thermal-cpufreq-0" + ], + "pattern":"^(.+)$" + }, + "Type":{ + "$id":"#/properties/CoolingDevices/items/properties/Type", + "type":"string", + "title":"The Type Schema", + "default":"", + "examples":[ + "CPU" + ], + "pattern":"^(.+)$" + } + } + } + } + } +} diff --git a/aidl/thermal/utils/power_files.cpp b/aidl/thermal/utils/power_files.cpp new file mode 100644 index 0000000..50cc5b6 --- /dev/null +++ b/aidl/thermal/utils/power_files.cpp @@ -0,0 +1,342 @@ + +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL) + +#include "power_files.h" + +#include +#include +#include +#include +#include +#include + +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> 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()); + 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()); + 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(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 *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(deltaEnergy) / static_cast(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( + 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::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::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 diff --git a/aidl/thermal/utils/power_files.h b/aidl/thermal/utils/power_files.h new file mode 100644 index 0000000..bcf8e82 --- /dev/null +++ b/aidl/thermal/utils/power_files.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#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> 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 &GetPowerStatusMap() const { + std::shared_lock _lock(power_status_map_mutex_); + return power_status_map_; + } + // Get power rail info map + const std::unordered_map &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 *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 energy_info_map_; + // The map to record the power data for each thermal sensor. + std::unordered_map 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 power_rail_info_map_; + // The set to store the energy source paths + std::unordered_set energy_path_set_; +}; + +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/powerhal_helper.cpp b/aidl/thermal/utils/powerhal_helper.cpp new file mode 100644 index 0000000..c40d262 --- /dev/null +++ b/aidl/thermal/utils/powerhal_helper.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "powerhal_helper.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 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 diff --git a/aidl/thermal/utils/powerhal_helper.h b/aidl/thermal/utils/powerhal_helper.h new file mode 100644 index 0000000..0769aa2 --- /dev/null +++ b/aidl/thermal/utils/powerhal_helper.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +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; + +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 power_hal_aidl_; + std::shared_ptr power_hal_ext_aidl_; + std::mutex lock_; +}; + +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/thermal_files.cpp b/aidl/thermal/utils/thermal_files.cpp new file mode 100644 index 0000000..26aaf45 --- /dev/null +++ b/aidl/thermal/utils/thermal_files.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL) + +#include "thermal_files.h" + +#include +#include +#include +#include +#include + +#include +#include + +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 diff --git a/aidl/thermal/utils/thermal_files.h b/aidl/thermal/utils/thermal_files.h new file mode 100644 index 0000000..4b83780 --- /dev/null +++ b/aidl/thermal/utils/thermal_files.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +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 thermal_name_to_path_map_; +}; + +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/thermal_info.cpp b/aidl/thermal/utils/thermal_info.cpp new file mode 100644 index 0000000..c00bf63 --- /dev/null +++ b/aidl/thermal/utils/thermal_info.cpp @@ -0,0 +1,1184 @@ +/* + * 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 +#include +#include +#include +#include + +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace thermal { +namespace implementation { + +constexpr std::string_view kPowerLinkDisabledProperty("vendor.disable.thermal.powerlink"); + +namespace { + +template +// Return false when failed parsing +bool getTypeFromString(std::string_view str, T *out) { + auto types = ::ndk::enum_range(); + 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::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 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 *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 linked_sensors; + std::vector linked_sensors_type; + std::vector trigger_sensors; + std::vector 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 *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::max()); + int max_release_step = std::numeric_limits::max(); + int max_throttle_step = std::numeric_limits::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 *throttling_info) { + std::array k_po; + k_po.fill(0.0); + std::array k_pu; + k_pu.fill(0.0); + std::array k_i; + k_i.fill(0.0); + std::array k_d; + k_d.fill(0.0); + std::array i_max; + i_max.fill(NAN); + std::array max_alloc_power; + max_alloc_power.fill(NAN); + std::array min_alloc_power; + min_alloc_power.fill(NAN); + std::array s_power; + s_power.fill(NAN); + std::array 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 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 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 *sensors_parsed) { + Json::Value sensors = config["Sensors"]; + std::size_t total_parsed = 0; + std::unordered_set 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 hot_thresholds; + hot_thresholds.fill(NAN); + std::array cold_thresholds; + cold_thresholds.fill(NAN); + std::array hot_hysteresis; + hot_hysteresis.fill(0.0); + std::array 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::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::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 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 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 *cooling_devices_parsed) { + Json::Value cooling_devices = config["CoolingDevices"]; + std::size_t total_parsed = 0; + std::unordered_set 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 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 *power_rails_parsed) { + Json::Value power_rails = config["PowerRails"]; + std::size_t total_parsed = 0; + std::unordered_set 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 linked_power_rails; + std::vector 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 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 +bool ParseStatsInfo(const Json::Value &stats_config, + const std::unordered_map &entity_info, StatsInfo *stats_info, + T min_value) { + if (stats_config.empty()) { + LOG(INFO) << "No stats config"; + return true; + } + std::variant> + 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(record_by_default_threshold_all_or_name_set_) << std::noboolalpha; + + Json::Value values = stats_config["RecordWithDefaultThreshold"]; + if (values.size()) { + if (std::get(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(); + 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>(record_by_default_threshold_all_or_name_set_) + .insert(name); + } + } else { + LOG(INFO) << "No stat by default threshold enabled."; + } + + std::unordered_map>> 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 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 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 + ? 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 &sensor_info_map_, + const std::unordered_map &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::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 diff --git a/aidl/thermal/utils/thermal_info.h b/aidl/thermal/utils/thermal_info.h new file mode 100644 index 0000000..5b2e8b8 --- /dev/null +++ b/aidl/thermal/utils/thermal_info.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace thermal { +namespace implementation { + +constexpr size_t kThrottlingSeverityCount = + std::distance(::ndk::enum_range().begin(), + ::ndk::enum_range().end()); +using ThrottlingArray = std::array(kThrottlingSeverityCount)>; +using CdevArray = std::array(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 +struct ThresholdList { + std::optional logging_name; + std::vector thresholds; + explicit ThresholdList(std::optional logging_name, std::vector 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 +struct StatsInfo { + // if bool, record all or none depending on flag + // if set, check name present in set + std::variant > + record_by_default_threshold_all_or_name_set_; + // map name to list of thresholds + std::unordered_map > > record_by_threshold; + void clear() { + record_by_default_threshold_all_or_name_set_ = false; + record_by_threshold.clear(); + } +}; + +struct StatsConfig { + StatsInfo sensor_stats_info; + StatsInfo 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 linked_sensors; + std::vector linked_sensors_type; + std::vector coefficients; + float offset; + std::vector trigger_sensors; + FormulaOption formula; +}; + +struct VirtualPowerRailInfo { + std::vector linked_power_rails; + std::vector 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 excluded_power_info_map; + std::unordered_map 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 virtual_sensor_info; + std::shared_ptr throttling_info; +}; + +struct CdevInfo { + CoolingType type; + std::string read_path; + std::string write_path; + std::vector state2power; + int max_state; +}; + +struct PowerRailInfo { + std::string rail; + int power_sample_count; + std::chrono::milliseconds power_sample_delay; + std::unique_ptr virtual_power_rail_info; +}; + +bool ParseThermalConfig(std::string_view config_path, Json::Value *config); +bool ParseSensorInfo(const Json::Value &config, + std::unordered_map *sensors_parsed); +bool ParseCoolingDevice(const Json::Value &config, + std::unordered_map *cooling_device_parsed); +bool ParsePowerRailInfo(const Json::Value &config, + std::unordered_map *power_rail_parsed); +bool ParseStatsConfig(const Json::Value &config, + const std::unordered_map &sensor_info_map_, + const std::unordered_map &cooling_device_info_map_, + StatsConfig *stats_config); +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/thermal_stats_helper.cpp b/aidl/thermal/utils/thermal_stats_helper.cpp new file mode 100644 index 0000000..daaaf1c --- /dev/null +++ b/aidl/thermal/utils/thermal_stats_helper.cpp @@ -0,0 +1,509 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "thermal_stats_helper.h" + +#include +#include +#include + +#include +#include +#include + +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 stats_client = nullptr; +std::shared_ptr 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> + &record_by_default_threshold_all_or_name_set_, + std::string_view name) { + if (std::holds_alternative(record_by_default_threshold_all_or_name_set_)) { + return std::get(record_by_default_threshold_all_or_name_set_); + } + return std::get>(record_by_default_threshold_all_or_name_set_) + .count(name.data()); +} + +template +int calculateThresholdBucket(const std::vector &thresholds, T value) { + if (thresholds.empty()) { + LOG(VERBOSE) << "No threshold present, so bucket is " << value << " as int."; + return static_cast(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 &sensor_info_map_, + const std::unordered_map &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 &request_stats_info, + const std::unordered_map &sensor_info_map_, + const std::unordered_map &cooling_device_info_map_) { + std::unique_lock _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 thresholds(kMaxStatsResidencyCount); + std::iota(thresholds.begin(), thresholds.end(), starting_state); + const auto logging_name = cdev + kCompressedThresholdSuffix.data(); + ThresholdList 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 &sensor_stats_info, + const std::unordered_map &sensor_info_map_) { + std::unique_lock _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( + 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 _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 _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 _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(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(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 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 &stats_client) { + int count_failed_reporting = 0; + std::unique_lock _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 &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 values(2); + values[0].set(sensor); + std::vector time_in_state_ms = processStatsRecordForReporting(stats_record); + const auto since_last_update_ms = std::chrono::duration_cast( + stats_record->cur_state_start_time - stats_record->last_stats_report_time); + values[1].set(since_last_update_ms.count()); + VendorAtomValue tmp; + for (auto &time_in_state : time_in_state_ms) { + tmp.set(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(0); + values.insert(values.end(), remaining_residency_buckets_count, tmp); + } + tmp.set(sensor_temp_stats.max_temp); + values.push_back(tmp); + tmp.set( + system_clock::to_time_t(sensor_temp_stats.max_temp_timestamp)); + values.push_back(tmp); + tmp.set(sensor_temp_stats.min_temp); + values.push_back(tmp); + tmp.set( + 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 &stats_client) { + int count_failed_reporting = 0; + std::unique_lock _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 &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 values(3); + values[0].set(sensor); + values[1].set(cdev); + std::vector time_in_state_ms = processStatsRecordForReporting(stats_record); + const auto since_last_update_ms = std::chrono::duration_cast( + stats_record->cur_state_start_time - stats_record->last_stats_report_time); + values[2].set(since_last_update_ms.count()); + VendorAtomValue tmp; + for (auto &time_in_state : time_in_state_ms) { + tmp.set(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 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 &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 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 &stats_client, + const int32_t &atom_id, std::vector &&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 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>> +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 diff --git a/aidl/thermal/utils/thermal_stats_helper.h b/aidl/thermal/utils/thermal_stats_helper.h new file mode 100644 index 0000000..ef2f811 --- /dev/null +++ b/aidl/thermal/utils/thermal_stats_helper.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +#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; + +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 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( + 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 +struct StatsByThreshold { + std::vector thresholds; + std::optional logging_name; + StatsRecord stats_record; + explicit StatsByThreshold(ThresholdList 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 +struct ThermalStats { + std::vector> stats_by_custom_threshold; + std::optional stats_by_default_threshold; +}; + +struct SensorTempStats : ThermalStats { + float max_temp = std::numeric_limits::min(); + SystemTimePoint max_temp_timestamp = SystemTimePoint::min(); + float min_temp = std::numeric_limits::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 &sensor_info_map_, + const std::unordered_map &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 GetSensorTempStatsSnapshot(); + // Get a snapshot of Thermal Stats Sensor Map till that point in time + std::unordered_map>> + GetSensorCoolingDeviceRequestStatsSnapshot(); + + private: + static constexpr std::chrono::milliseconds kUpdateIntervalMs = + std::chrono::duration_cast(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 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>> + sensor_cdev_request_stats_map_; + + bool initializeSensorTempStats( + const StatsInfo &sensor_stats_info, + const std::unordered_map &sensor_info_map_); + bool initializeSensorCdevRequestStats( + const StatsInfo &request_stats_info, + const std::unordered_map &sensor_info_map_, + const std::unordered_map &cooling_device_info_map_); + void updateStatsRecord(StatsRecord *stats_record, int new_state); + int reportAllSensorTempStats(const std::shared_ptr &stats_client); + bool reportSensorTempStats(const std::shared_ptr &stats_client, std::string_view sensor, + const SensorTempStats &sensor_temp_stats, StatsRecord *stats_record); + int reportAllSensorCdevRequestStats(const std::shared_ptr &stats_client); + bool reportSensorCdevRequestStats(const std::shared_ptr &stats_client, + std::string_view sensor, std::string_view cdev, + StatsRecord *stats_record); + bool reportAtom(const std::shared_ptr &stats_client, const int32_t &atom_id, + std::vector &&values); + std::vector processStatsRecordForReporting(StatsRecord *stats_record); + StatsRecord restoreStatsRecordOnFailure(StatsRecord &&stats_record_before_failure); +}; + +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/thermal_throttling.cpp b/aidl/thermal/utils/thermal_throttling.cpp new file mode 100644 index 0000000..8aac95d --- /dev/null +++ b/aidl/thermal/utils/thermal_throttling.cpp @@ -0,0 +1,767 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL) + +#include "thermal_throttling.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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()) { + size_t state = static_cast(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 _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::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(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 &throttling_info, + const std::unordered_map &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(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::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::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(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(throttling_status.tran_cycle) / + static_cast(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(power_budget)); + ATRACE_INT((sensor_name + std::string("-s_power")).c_str(), + static_cast(sensor_info.throttling_info->s_power[target_state])); + ATRACE_INT((sensor_name + std::string("-time_elapsed_ms")).c_str(), + static_cast(time_elapsed_ms.count())); + ATRACE_INT((sensor_name + std::string("-budget_transient")).c_str(), + static_cast(budget_transient)); + ATRACE_INT((sensor_name + std::string("-i")).c_str(), + static_cast(throttling_status.i_budget)); + ATRACE_INT((sensor_name + std::string("-target_state")).c_str(), + static_cast(target_state)); + + ATRACE_INT((sensor_name + std::string("-err")).c_str(), static_cast(err / sensor_info.multiplier)); + ATRACE_INT((sensor_name + std::string("-p")).c_str(), static_cast(p)); + ATRACE_INT((sensor_name + std::string("-d")).c_str(), static_cast(d)); + ATRACE_INT((sensor_name + std::string("-temp")).c_str(), static_cast(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 &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(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(curr_severity)])); + + ATRACE_INT((std::string(sensor_name) + std::string("-") + + excluded_power_info_pair.first + std::string("-avg_power")) + .c_str(), + static_cast(last_updated_avg_power)); + } + } + + ATRACE_INT((std::string(sensor_name) + std::string("-excluded_power")).c_str(), + static_cast(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 &power_status_map, + const std::unordered_map &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 allocated_cdev; + std::string log_buf; + + std::unique_lock _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(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(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(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::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::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 &cooling_device_info_map) { + size_t i; + + std::unique_lock _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(i); + } + + return; +} + +void ThermalThrottling::updateCdevRequestBySeverity(std::string_view sensor_name, + const SensorInfo &sensor_info, + ThrottlingSeverity curr_severity) { + std::unique_lock _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(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 &cooling_device_info_map, + const std::unordered_map &power_status_map, + const ThrottlingSeverity severity, const SensorInfo &sensor_info) { + ATRACE_CALL(); + std::unique_lock _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(severity)]) { + is_over_budget = false; + } + } else { + if (avg_power > + binded_cdev_info_pair.second.power_thresholds[static_cast(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(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( + binded_cdev_info_pair.second.power_thresholds[static_cast(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(max_state)) { + release_step--; + } + } else { + release_step = 0; + } + break; + case ReleaseLogic::DECREASE: + if (!is_over_budget) { + if (release_step < static_cast(max_state)) { + release_step++; + } + } else { + release_step = 0; + } + break; + case ReleaseLogic::STEPWISE: + if (!is_over_budget) { + if (release_step < static_cast(max_state)) { + release_step++; + } + } else { + if (std::abs(release_step) < static_cast(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 &power_status_map, + const std::unordered_map &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 *cooling_devices_to_update, + ThermalStatsHelper *thermal_stats_helper) { + int release_step = 0; + std::unique_lock _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(curr_severity)]; + const auto cdev_floor = + binded_cdev_info.cdev_floor_with_power_link[static_cast(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 _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 _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 diff --git a/aidl/thermal/utils/thermal_throttling.h b/aidl/thermal/utils/thermal_throttling.h new file mode 100644 index 0000000..e83ee06 --- /dev/null +++ b/aidl/thermal/utils/thermal_throttling.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#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 pid_power_budget_map; + std::unordered_map pid_cdev_request_map; + std::unordered_map hardlimit_cdev_request_map; + std::unordered_map throttling_release_map; + std::unordered_map 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 &throttling_info, + const std::unordered_map &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 &GetThermalThrottlingStatusMap() + const { + std::shared_lock _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 &power_status_map, + const std::unordered_map &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 *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 &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 &power_status_map, + const std::unordered_map &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 &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 &cooling_device_info_map, + const std::unordered_map &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 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>> cdev_all_request_map_; +}; + +} // namespace implementation +} // namespace thermal +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/aidl/thermal/utils/thermal_watcher.cpp b/aidl/thermal/utils/thermal_watcher.cpp new file mode 100644 index 0000000..d8bc92e --- /dev/null +++ b/aidl/thermal/utils/thermal_watcher.cpp @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL) + +#include "thermal_watcher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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(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(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(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(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 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(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(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(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 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(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 &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 &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 *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 *sensors_set) { + int err = 0, done = 0, tz_id = -1; + + std::unique_ptr 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 sensors; + + auto time_elapsed_ms = std::chrono::duration_cast(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 diff --git a/aidl/thermal/utils/thermal_watcher.h b/aidl/thermal/utils/thermal_watcher.h new file mode 100644 index 0000000..8f8b398 --- /dev/null +++ b/aidl/thermal/utils/thermal_watcher.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace thermal { +namespace implementation { + +using ::android::base::boot_clock; +using ::android::base::unique_fd; +using WatcherCallback = std::function &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 &sensors_to_watch); + // For monitoring thermal genl events. + void registerFilesToWatchNl(const std::set &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 *sensor_name); + + // Parse thermal netlink message + void parseGenlink(std::set *sensor_name); + + // Maps watcher filer descriptor to watched file path. + std::unordered_map 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 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