aidl: Import Pixel Thermal HAL aidl implementation
authorTim Zimmermann <tim@linux4.de>
Sun, 12 Nov 2023 05:43:40 +0000 (06:43 +0100)
committerTim Zimmermann <tim@linux4.de>
Sat, 20 Jan 2024 12:52:29 +0000 (13:52 +0100)
* From hardware/google/pixel @ android-14.0.0_r15

Change-Id: I30e35c8e4ef58956f849d64e184aa7e37ec67ef9

27 files changed:
aidl/thermal/Android.bp [new file with mode: 0644]
aidl/thermal/Thermal.cpp [new file with mode: 0644]
aidl/thermal/Thermal.h [new file with mode: 0644]
aidl/thermal/android.hardware.thermal-service.pixel.rc [new file with mode: 0644]
aidl/thermal/android.hardware.thermal-service.pixel.xml [new file with mode: 0644]
aidl/thermal/init.thermal.logging.sh [new file with mode: 0755]
aidl/thermal/init.thermal.symlinks.sh [new file with mode: 0755]
aidl/thermal/pixel-thermal-logd.rc [new file with mode: 0644]
aidl/thermal/pixel-thermal-symlinks.rc [new file with mode: 0644]
aidl/thermal/service.cpp [new file with mode: 0644]
aidl/thermal/thermal-helper.cpp [new file with mode: 0644]
aidl/thermal/thermal-helper.h [new file with mode: 0644]
aidl/thermal/utils/config_schema.json [new file with mode: 0644]
aidl/thermal/utils/power_files.cpp [new file with mode: 0644]
aidl/thermal/utils/power_files.h [new file with mode: 0644]
aidl/thermal/utils/powerhal_helper.cpp [new file with mode: 0644]
aidl/thermal/utils/powerhal_helper.h [new file with mode: 0644]
aidl/thermal/utils/thermal_files.cpp [new file with mode: 0644]
aidl/thermal/utils/thermal_files.h [new file with mode: 0644]
aidl/thermal/utils/thermal_info.cpp [new file with mode: 0644]
aidl/thermal/utils/thermal_info.h [new file with mode: 0644]
aidl/thermal/utils/thermal_stats_helper.cpp [new file with mode: 0644]
aidl/thermal/utils/thermal_stats_helper.h [new file with mode: 0644]
aidl/thermal/utils/thermal_throttling.cpp [new file with mode: 0644]
aidl/thermal/utils/thermal_throttling.h [new file with mode: 0644]
aidl/thermal/utils/thermal_watcher.cpp [new file with mode: 0644]
aidl/thermal/utils/thermal_watcher.h [new file with mode: 0644]

diff --git a/aidl/thermal/Android.bp b/aidl/thermal/Android.bp
new file mode 100644 (file)
index 0000000..5f426f9
--- /dev/null
@@ -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 (file)
index 0000000..776341d
--- /dev/null
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
+
+#include "Thermal.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <utils/Trace.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+namespace {
+
+ndk::ScopedAStatus initErrorStatus() {
+    return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_STATE,
+                                                            "ThermalHAL not initialized properly.");
+}
+
+ndk::ScopedAStatus readErrorStatus() {
+    return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+            EX_ILLEGAL_STATE, "ThermalHal cannot read any sensor data");
+}
+
+bool interfacesEqual(const std::shared_ptr<::ndk::ICInterface> left,
+                     const std::shared_ptr<::ndk::ICInterface> right) {
+    if (left == nullptr || right == nullptr || !left->isRemote() || !right->isRemote()) {
+        return left == right;
+    }
+    return left->asBinder() == right->asBinder();
+}
+
+}  // namespace
+
+Thermal::Thermal()
+    : thermal_helper_(
+              std::bind(&Thermal::sendThermalChangedCallback, this, std::placeholders::_1)) {}
+
+ndk::ScopedAStatus Thermal::getTemperatures(std::vector<Temperature> *_aidl_return) {
+    return getFilteredTemperatures(false, TemperatureType::UNKNOWN, _aidl_return);
+}
+
+ndk::ScopedAStatus Thermal::getTemperaturesWithType(TemperatureType type,
+                                                    std::vector<Temperature> *_aidl_return) {
+    return getFilteredTemperatures(true, type, _aidl_return);
+}
+
+ndk::ScopedAStatus Thermal::getFilteredTemperatures(bool filterType, TemperatureType type,
+                                                    std::vector<Temperature> *_aidl_return) {
+    *_aidl_return = {};
+    if (!thermal_helper_.isInitializedOk()) {
+        return initErrorStatus();
+    }
+    if (!thermal_helper_.fillCurrentTemperatures(filterType, false, type, _aidl_return)) {
+        return readErrorStatus();
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Thermal::getCoolingDevices(std::vector<CoolingDevice> *_aidl_return) {
+    return getFilteredCoolingDevices(false, CoolingType::BATTERY, _aidl_return);
+}
+
+ndk::ScopedAStatus Thermal::getCoolingDevicesWithType(CoolingType type,
+                                                      std::vector<CoolingDevice> *_aidl_return) {
+    return getFilteredCoolingDevices(true, type, _aidl_return);
+}
+
+ndk::ScopedAStatus Thermal::getFilteredCoolingDevices(bool filterType, CoolingType type,
+                                                      std::vector<CoolingDevice> *_aidl_return) {
+    *_aidl_return = {};
+    if (!thermal_helper_.isInitializedOk()) {
+        return initErrorStatus();
+    }
+    if (!thermal_helper_.fillCurrentCoolingDevices(filterType, type, _aidl_return)) {
+        return readErrorStatus();
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Thermal::getTemperatureThresholds(
+        std::vector<TemperatureThreshold> *_aidl_return) {
+    *_aidl_return = {};
+    return getFilteredTemperatureThresholds(false, TemperatureType::UNKNOWN, _aidl_return);
+}
+
+ndk::ScopedAStatus Thermal::getTemperatureThresholdsWithType(
+        TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return) {
+    return getFilteredTemperatureThresholds(true, type, _aidl_return);
+}
+
+ndk::ScopedAStatus Thermal::getFilteredTemperatureThresholds(
+        bool filterType, TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return) {
+    *_aidl_return = {};
+    if (!thermal_helper_.isInitializedOk()) {
+        return initErrorStatus();
+    }
+    if (!thermal_helper_.fillTemperatureThresholds(filterType, type, _aidl_return)) {
+        return readErrorStatus();
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Thermal::registerThermalChangedCallback(
+        const std::shared_ptr<IThermalChangedCallback> &callback) {
+    ATRACE_CALL();
+    return registerThermalChangedCallback(callback, false, TemperatureType::UNKNOWN);
+}
+
+ndk::ScopedAStatus Thermal::registerThermalChangedCallbackWithType(
+        const std::shared_ptr<IThermalChangedCallback> &callback, TemperatureType type) {
+    ATRACE_CALL();
+    return registerThermalChangedCallback(callback, true, type);
+}
+
+ndk::ScopedAStatus Thermal::unregisterThermalChangedCallback(
+        const std::shared_ptr<IThermalChangedCallback> &callback) {
+    if (callback == nullptr) {
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                "Invalid nullptr callback");
+    }
+    bool removed = false;
+    std::lock_guard<std::mutex> _lock(thermal_callback_mutex_);
+    callbacks_.erase(
+            std::remove_if(
+                    callbacks_.begin(), callbacks_.end(),
+                    [&](const CallbackSetting &c) {
+                        if (interfacesEqual(c.callback, callback)) {
+                            LOG(INFO)
+                                    << "a callback has been unregistered to ThermalHAL, isFilter: "
+                                    << c.is_filter_type << " Type: " << toString(c.type);
+                            removed = true;
+                            return true;
+                        }
+                        return false;
+                    }),
+            callbacks_.end());
+    if (!removed) {
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                "Callback wasn't registered");
+    }
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Thermal::registerThermalChangedCallback(
+        const std::shared_ptr<IThermalChangedCallback> &callback, bool filterType,
+        TemperatureType type) {
+    ATRACE_CALL();
+    if (callback == nullptr) {
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                "Invalid nullptr callback");
+    }
+    if (!thermal_helper_.isInitializedOk()) {
+        return initErrorStatus();
+    }
+    std::lock_guard<std::mutex> _lock(thermal_callback_mutex_);
+    if (std::any_of(callbacks_.begin(), callbacks_.end(), [&](const CallbackSetting &c) {
+            return interfacesEqual(c.callback, callback);
+        })) {
+        return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_ILLEGAL_ARGUMENT,
+                                                                "Callback already registered");
+    }
+    auto c = callbacks_.emplace_back(callback, filterType, type);
+    LOG(INFO) << "a callback has been registered to ThermalHAL, isFilter: " << c.is_filter_type
+              << " Type: " << toString(c.type);
+    // Send notification right away after successful thermal callback registration
+    std::function<void()> handler = [this, c, filterType, type]() {
+        std::vector<Temperature> temperatures;
+        if (thermal_helper_.fillCurrentTemperatures(filterType, true, type, &temperatures)) {
+            for (const auto &t : temperatures) {
+                if (!filterType || t.type == type) {
+                    LOG(INFO) << "Sending notification: "
+                              << " Type: " << toString(t.type) << " Name: " << t.name
+                              << " CurrentValue: " << t.value
+                              << " ThrottlingStatus: " << toString(t.throttlingStatus);
+                    c.callback->notifyThrottling(t);
+                }
+            }
+        }
+    };
+    looper_.addEvent(Looper::Event{handler});
+    return ndk::ScopedAStatus::ok();
+}
+
+void Thermal::sendThermalChangedCallback(const Temperature &t) {
+    ATRACE_CALL();
+    std::lock_guard<std::mutex> _lock(thermal_callback_mutex_);
+    LOG(VERBOSE) << "Sending notification: "
+                 << " Type: " << toString(t.type) << " Name: " << t.name
+                 << " CurrentValue: " << t.value
+                 << " ThrottlingStatus: " << toString(t.throttlingStatus);
+
+    callbacks_.erase(std::remove_if(callbacks_.begin(), callbacks_.end(),
+                                    [&](const CallbackSetting &c) {
+                                        if (!c.is_filter_type || t.type == c.type) {
+                                            ::ndk::ScopedAStatus ret =
+                                                    c.callback->notifyThrottling(t);
+                                            if (!ret.isOk()) {
+                                                LOG(ERROR) << "a Thermal callback is dead, removed "
+                                                              "from callback list.";
+                                                return true;
+                                            }
+                                            return false;
+                                        }
+                                        return false;
+                                    }),
+                     callbacks_.end());
+}
+
+void Thermal::dumpVirtualSensorInfo(std::ostringstream *dump_buf) {
+    *dump_buf << "getVirtualSensorInfo:" << std::endl;
+    const auto &map = thermal_helper_.GetSensorInfoMap();
+    for (const auto &sensor_info_pair : map) {
+        if (sensor_info_pair.second.virtual_sensor_info != nullptr) {
+            *dump_buf << " Name: " << sensor_info_pair.first << std::endl;
+            *dump_buf << "  LinkedSensorName: [";
+            for (size_t i = 0;
+                 i < sensor_info_pair.second.virtual_sensor_info->linked_sensors.size(); i++) {
+                *dump_buf << sensor_info_pair.second.virtual_sensor_info->linked_sensors[i] << " ";
+            }
+            *dump_buf << "]" << std::endl;
+            *dump_buf << "  LinkedSensorCoefficient: [";
+            for (size_t i = 0; i < sensor_info_pair.second.virtual_sensor_info->coefficients.size();
+                 i++) {
+                *dump_buf << sensor_info_pair.second.virtual_sensor_info->coefficients[i] << " ";
+            }
+            *dump_buf << "]" << std::endl;
+            *dump_buf << "  Offset: " << sensor_info_pair.second.virtual_sensor_info->offset
+                      << std::endl;
+            *dump_buf << "  Trigger Sensor: ";
+            if (sensor_info_pair.second.virtual_sensor_info->trigger_sensors.empty()) {
+                *dump_buf << "N/A" << std::endl;
+            } else {
+                for (size_t i = 0;
+                     i < sensor_info_pair.second.virtual_sensor_info->trigger_sensors.size(); i++) {
+                    *dump_buf << sensor_info_pair.second.virtual_sensor_info->trigger_sensors[i]
+                              << " ";
+                }
+                *dump_buf << std::endl;
+            }
+            *dump_buf << "  Formula: ";
+            switch (sensor_info_pair.second.virtual_sensor_info->formula) {
+                case FormulaOption::COUNT_THRESHOLD:
+                    *dump_buf << "COUNT_THRESHOLD";
+                    break;
+                case FormulaOption::WEIGHTED_AVG:
+                    *dump_buf << "WEIGHTED_AVG";
+                    break;
+                case FormulaOption::MAXIMUM:
+                    *dump_buf << "MAXIMUM";
+                    break;
+                case FormulaOption::MINIMUM:
+                    *dump_buf << "MINIMUM";
+                    break;
+                default:
+                    *dump_buf << "NONE";
+                    break;
+            }
+
+            *dump_buf << std::endl;
+        }
+    }
+}
+
+void Thermal::dumpThrottlingInfo(std::ostringstream *dump_buf) {
+    *dump_buf << "getThrottlingInfo:" << std::endl;
+    const auto &map = thermal_helper_.GetSensorInfoMap();
+    const auto &thermal_throttling_status_map = thermal_helper_.GetThermalThrottlingStatusMap();
+    for (const auto &name_info_pair : map) {
+        if (name_info_pair.second.throttling_info == nullptr) {
+            continue;
+        }
+        if (name_info_pair.second.throttling_info->binded_cdev_info_map.size()) {
+            if (thermal_throttling_status_map.find(name_info_pair.first) ==
+                thermal_throttling_status_map.end()) {
+                continue;
+            }
+            *dump_buf << " Name: " << name_info_pair.first << std::endl;
+            if (thermal_throttling_status_map.at(name_info_pair.first)
+                        .pid_power_budget_map.size()) {
+                *dump_buf << "  PID Info:" << std::endl;
+                *dump_buf << "   K_po: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->k_po[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   K_pu: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->k_pu[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   K_i: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->k_i[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   K_d: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->k_d[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   i_max: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->i_max[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   max_alloc_power: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->max_alloc_power[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   min_alloc_power: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->min_alloc_power[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   s_power: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->s_power[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "   i_cutoff: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << name_info_pair.second.throttling_info->i_cutoff[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+            }
+            *dump_buf << "  Binded CDEV Info:" << std::endl;
+            for (const auto &binded_cdev_info_pair :
+                 name_info_pair.second.throttling_info->binded_cdev_info_map) {
+                *dump_buf << "   Cooling device name: " << binded_cdev_info_pair.first << std::endl;
+                if (thermal_throttling_status_map.at(name_info_pair.first)
+                            .pid_power_budget_map.size()) {
+                    *dump_buf << "    WeightForPID: [";
+                    for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                        *dump_buf << binded_cdev_info_pair.second.cdev_weight_for_pid[i] << " ";
+                    }
+                    *dump_buf << "]" << std::endl;
+                }
+                *dump_buf << "    Ceiling: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << binded_cdev_info_pair.second.cdev_ceiling[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                *dump_buf << "    Hard limit: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    *dump_buf << binded_cdev_info_pair.second.limit_info[i] << " ";
+                }
+                *dump_buf << "]" << std::endl;
+
+                if (!binded_cdev_info_pair.second.power_rail.empty()) {
+                    *dump_buf << "    Binded power rail: "
+                              << binded_cdev_info_pair.second.power_rail << std::endl;
+                    *dump_buf << "    Power threshold: [";
+                    for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                        *dump_buf << binded_cdev_info_pair.second.power_thresholds[i] << " ";
+                    }
+                    *dump_buf << "]" << std::endl;
+                    *dump_buf << "    Floor with PowerLink: [";
+                    for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                        *dump_buf << binded_cdev_info_pair.second.cdev_floor_with_power_link[i]
+                                  << " ";
+                    }
+                    *dump_buf << "]" << std::endl;
+                    *dump_buf << "    Release logic: ";
+                    switch (binded_cdev_info_pair.second.release_logic) {
+                        case ReleaseLogic::INCREASE:
+                            *dump_buf << "INCREASE";
+                            break;
+                        case ReleaseLogic::DECREASE:
+                            *dump_buf << "DECREASE";
+                            break;
+                        case ReleaseLogic::STEPWISE:
+                            *dump_buf << "STEPWISE";
+                            break;
+                        case ReleaseLogic::RELEASE_TO_FLOOR:
+                            *dump_buf << "RELEASE_TO_FLOOR";
+                            break;
+                        default:
+                            *dump_buf << "NONE";
+                            break;
+                    }
+                    *dump_buf << std::endl;
+                    *dump_buf << "    high_power_check: " << std::boolalpha
+                              << binded_cdev_info_pair.second.high_power_check << std::endl;
+                    *dump_buf << "    throttling_with_power_link: " << std::boolalpha
+                              << binded_cdev_info_pair.second.throttling_with_power_link
+                              << std::endl;
+                }
+            }
+        }
+    }
+}
+
+void Thermal::dumpThrottlingRequestStatus(std::ostringstream *dump_buf) {
+    const auto &thermal_throttling_status_map = thermal_helper_.GetThermalThrottlingStatusMap();
+    if (!thermal_throttling_status_map.size()) {
+        return;
+    }
+    *dump_buf << "getThrottlingRequestStatus:" << std::endl;
+    for (const auto &thermal_throttling_status_pair : thermal_throttling_status_map) {
+        *dump_buf << " Name: " << thermal_throttling_status_pair.first << std::endl;
+        if (thermal_throttling_status_pair.second.pid_power_budget_map.size()) {
+            *dump_buf << "  power budget request state" << std::endl;
+            for (const auto &request_pair :
+                 thermal_throttling_status_pair.second.pid_power_budget_map) {
+                *dump_buf << "   " << request_pair.first << ": " << request_pair.second
+                          << std::endl;
+            }
+        }
+        if (thermal_throttling_status_pair.second.pid_cdev_request_map.size()) {
+            *dump_buf << "  pid cdev request state" << std::endl;
+            for (const auto &request_pair :
+                 thermal_throttling_status_pair.second.pid_cdev_request_map) {
+                *dump_buf << "   " << request_pair.first << ": " << request_pair.second
+                          << std::endl;
+            }
+        }
+        if (thermal_throttling_status_pair.second.hardlimit_cdev_request_map.size()) {
+            *dump_buf << "  hard limit cdev request state" << std::endl;
+            for (const auto &request_pair :
+                 thermal_throttling_status_pair.second.hardlimit_cdev_request_map) {
+                *dump_buf << "   " << request_pair.first << ": " << request_pair.second
+                          << std::endl;
+            }
+        }
+        if (thermal_throttling_status_pair.second.throttling_release_map.size()) {
+            *dump_buf << "  cdev release state" << std::endl;
+            for (const auto &request_pair :
+                 thermal_throttling_status_pair.second.throttling_release_map) {
+                *dump_buf << "   " << request_pair.first << ": " << request_pair.second
+                          << std::endl;
+            }
+        }
+        if (thermal_throttling_status_pair.second.cdev_status_map.size()) {
+            *dump_buf << "  cdev request state" << std::endl;
+            for (const auto &request_pair : thermal_throttling_status_pair.second.cdev_status_map) {
+                *dump_buf << "   " << request_pair.first << ": " << request_pair.second
+                          << std::endl;
+            }
+        }
+    }
+}
+
+void Thermal::dumpPowerRailInfo(std::ostringstream *dump_buf) {
+    const auto &power_rail_info_map = thermal_helper_.GetPowerRailInfoMap();
+    const auto &power_status_map = thermal_helper_.GetPowerStatusMap();
+
+    *dump_buf << "getPowerRailInfo:" << std::endl;
+    for (const auto &power_rail_pair : power_rail_info_map) {
+        *dump_buf << " Power Rail: " << power_rail_pair.first << std::endl;
+        *dump_buf << "  Power Sample Count: " << power_rail_pair.second.power_sample_count
+                  << std::endl;
+        *dump_buf << "  Power Sample Delay: " << power_rail_pair.second.power_sample_delay.count()
+                  << std::endl;
+        if (power_status_map.count(power_rail_pair.first)) {
+            auto power_history = power_status_map.at(power_rail_pair.first).power_history;
+            *dump_buf << "  Last Updated AVG Power: "
+                      << power_status_map.at(power_rail_pair.first).last_updated_avg_power << " mW"
+                      << std::endl;
+            if (power_rail_pair.second.virtual_power_rail_info != nullptr) {
+                *dump_buf << "  Formula=";
+                switch (power_rail_pair.second.virtual_power_rail_info->formula) {
+                    case FormulaOption::COUNT_THRESHOLD:
+                        *dump_buf << "COUNT_THRESHOLD";
+                        break;
+                    case FormulaOption::WEIGHTED_AVG:
+                        *dump_buf << "WEIGHTED_AVG";
+                        break;
+                    case FormulaOption::MAXIMUM:
+                        *dump_buf << "MAXIMUM";
+                        break;
+                    case FormulaOption::MINIMUM:
+                        *dump_buf << "MINIMUM";
+                        break;
+                    default:
+                        *dump_buf << "NONE";
+                        break;
+                }
+                *dump_buf << std::endl;
+            }
+            for (size_t i = 0; i < power_history.size(); ++i) {
+                if (power_rail_pair.second.virtual_power_rail_info != nullptr) {
+                    *dump_buf
+                            << "  Linked power rail "
+                            << power_rail_pair.second.virtual_power_rail_info->linked_power_rails[i]
+                            << std::endl;
+                    *dump_buf << "   Coefficient="
+                              << power_rail_pair.second.virtual_power_rail_info->coefficients[i]
+                              << std::endl;
+                    *dump_buf << "   Power Samples: ";
+                } else {
+                    *dump_buf << "  Power Samples: ";
+                }
+                while (power_history[i].size() > 0) {
+                    const auto power_sample = power_history[i].front();
+                    power_history[i].pop();
+                    *dump_buf << "(T=" << power_sample.duration
+                              << ", uWs=" << power_sample.energy_counter << ") ";
+                }
+                *dump_buf << std::endl;
+            }
+        }
+    }
+}
+
+void Thermal::dumpStatsRecord(std::ostringstream *dump_buf, const StatsRecord &stats_record,
+                              std::string_view line_prefix) {
+    const auto now = boot_clock::now();
+    *dump_buf << line_prefix << "Time Since Last Stats Report: "
+              << std::chrono::duration_cast<std::chrono::minutes>(
+                         now - stats_record.last_stats_report_time)
+                         .count()
+              << " mins" << std::endl;
+    *dump_buf << line_prefix << "Time in State ms: [";
+    for (const auto &time_in_state : stats_record.time_in_state_ms) {
+        *dump_buf << time_in_state.count() << " ";
+    }
+    *dump_buf << "]" << std::endl;
+}
+
+void Thermal::dumpThermalStats(std::ostringstream *dump_buf) {
+    *dump_buf << "getThermalStatsInfo:" << std::endl;
+    *dump_buf << " Sensor Temp Stats Info:" << std::endl;
+    const auto &sensor_temp_stats_map_ = thermal_helper_.GetSensorTempStatsSnapshot();
+    const std::string sensor_temp_stats_line_prefix("    ");
+    for (const auto &sensor_temp_stats_pair : sensor_temp_stats_map_) {
+        *dump_buf << "  Sensor Name: " << sensor_temp_stats_pair.first << std::endl;
+        const auto &sensor_temp_stats = sensor_temp_stats_pair.second;
+        *dump_buf << "   Max Temp: " << sensor_temp_stats.max_temp << ", TimeStamp: "
+                  << system_clock::to_time_t(sensor_temp_stats.max_temp_timestamp) << std::endl;
+        *dump_buf << "   Min Temp: " << sensor_temp_stats.min_temp << ", TimeStamp: "
+                  << system_clock::to_time_t(sensor_temp_stats.min_temp_timestamp) << std::endl;
+        for (const auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) {
+            *dump_buf << "   Record by Threshold: [";
+            for (const auto &threshold : stats_by_threshold.thresholds) {
+                *dump_buf << threshold << " ";
+            }
+            *dump_buf << "]" << std::endl;
+            if (stats_by_threshold.logging_name.has_value()) {
+                *dump_buf << "    Logging Name: " << stats_by_threshold.logging_name.value()
+                          << std::endl;
+            }
+            dumpStatsRecord(dump_buf, stats_by_threshold.stats_record,
+                            sensor_temp_stats_line_prefix);
+        }
+
+        if (sensor_temp_stats.stats_by_default_threshold.has_value()) {
+            *dump_buf << "   Record by Severity:" << std::endl;
+            dumpStatsRecord(dump_buf, sensor_temp_stats.stats_by_default_threshold.value(),
+                            sensor_temp_stats_line_prefix);
+        }
+    }
+    *dump_buf << " Sensor Cdev Request Stats Info:" << std::endl;
+    const auto &sensor_cdev_request_stats_map_ =
+            thermal_helper_.GetSensorCoolingDeviceRequestStatsSnapshot();
+    const std::string sensor_cdev_request_stats_line_prefix("     ");
+    for (const auto &sensor_cdev_request_stats_pair : sensor_cdev_request_stats_map_) {
+        *dump_buf << "  Sensor Name: " << sensor_cdev_request_stats_pair.first << std::endl;
+        for (const auto &cdev_request_stats_pair : sensor_cdev_request_stats_pair.second) {
+            *dump_buf << "   Cooling Device Name: " << cdev_request_stats_pair.first << std::endl;
+            const auto &request_stats = cdev_request_stats_pair.second;
+            for (const auto &stats_by_threshold : request_stats.stats_by_custom_threshold) {
+                *dump_buf << "    Record by Threshold: [";
+                for (const auto &threshold : stats_by_threshold.thresholds) {
+                    *dump_buf << threshold << " ";
+                }
+                *dump_buf << "]" << std::endl;
+                if (stats_by_threshold.logging_name.has_value()) {
+                    *dump_buf << "     Logging Name: " << stats_by_threshold.logging_name.value()
+                              << std::endl;
+                }
+                dumpStatsRecord(dump_buf, stats_by_threshold.stats_record,
+                                sensor_cdev_request_stats_line_prefix);
+            }
+            if (request_stats.stats_by_default_threshold.has_value()) {
+                *dump_buf << "    Record by All State" << std::endl;
+                dumpStatsRecord(dump_buf, request_stats.stats_by_default_threshold.value(),
+                                sensor_cdev_request_stats_line_prefix);
+            }
+        }
+    }
+}
+
+void Thermal::dumpThermalData(int fd) {
+    std::ostringstream dump_buf;
+
+    if (!thermal_helper_.isInitializedOk()) {
+        dump_buf << "ThermalHAL not initialized properly." << std::endl;
+    } else {
+        const auto &sensor_status_map = thermal_helper_.GetSensorStatusMap();
+        {
+            dump_buf << "getCachedTemperatures:" << std::endl;
+            boot_clock::time_point now = boot_clock::now();
+            for (const auto &sensor_status_pair : sensor_status_map) {
+                if ((sensor_status_pair.second.thermal_cached.timestamp) ==
+                    boot_clock::time_point::min()) {
+                    continue;
+                }
+                dump_buf << " Name: " << sensor_status_pair.first
+                         << " CachedValue: " << sensor_status_pair.second.thermal_cached.temp
+                         << " TimeToCache: "
+                         << std::chrono::duration_cast<std::chrono::milliseconds>(
+                                    now - sensor_status_pair.second.thermal_cached.timestamp)
+                                    .count()
+                         << "ms" << std::endl;
+            }
+        }
+        {
+            dump_buf << "getEmulTemperatures:" << std::endl;
+            for (const auto &sensor_status_pair : sensor_status_map) {
+                if (sensor_status_pair.second.emul_setting == nullptr) {
+                    continue;
+                }
+                dump_buf << " Name: " << sensor_status_pair.first
+                         << " EmulTemp: " << sensor_status_pair.second.emul_setting->emul_temp
+                         << " EmulSeverity: "
+                         << sensor_status_pair.second.emul_setting->emul_severity << std::endl;
+            }
+        }
+        {
+            const auto &map = thermal_helper_.GetSensorInfoMap();
+            dump_buf << "getCurrentTemperatures:" << std::endl;
+            Temperature temp_2_0;
+            for (const auto &name_info_pair : map) {
+                thermal_helper_.readTemperature(name_info_pair.first, &temp_2_0, nullptr, true);
+                dump_buf << " Type: " << toString(temp_2_0.type)
+                         << " Name: " << name_info_pair.first << " CurrentValue: " << temp_2_0.value
+                         << " ThrottlingStatus: " << toString(temp_2_0.throttlingStatus)
+                         << std::endl;
+            }
+            dump_buf << "getTemperatureThresholds:" << std::endl;
+            for (const auto &name_info_pair : map) {
+                if (!name_info_pair.second.is_watch) {
+                    continue;
+                }
+                dump_buf << " Type: " << toString(name_info_pair.second.type)
+                         << " Name: " << name_info_pair.first;
+                dump_buf << " hotThrottlingThreshold: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    dump_buf << name_info_pair.second.hot_thresholds[i] << " ";
+                }
+                dump_buf << "] coldThrottlingThreshold: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    dump_buf << name_info_pair.second.cold_thresholds[i] << " ";
+                }
+                dump_buf << "] vrThrottlingThreshold: " << name_info_pair.second.vr_threshold;
+                dump_buf << std::endl;
+            }
+            dump_buf << "getHysteresis:" << std::endl;
+            for (const auto &name_info_pair : map) {
+                if (!name_info_pair.second.is_watch) {
+                    continue;
+                }
+                dump_buf << " Name: " << name_info_pair.first;
+                dump_buf << " hotHysteresis: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    dump_buf << name_info_pair.second.hot_hysteresis[i] << " ";
+                }
+                dump_buf << "] coldHysteresis: [";
+                for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                    dump_buf << name_info_pair.second.cold_hysteresis[i] << " ";
+                }
+                dump_buf << "]" << std::endl;
+            }
+        }
+        {
+            dump_buf << "getCurrentCoolingDevices:" << std::endl;
+            std::vector<CoolingDevice> cooling_devices;
+            if (!thermal_helper_.fillCurrentCoolingDevices(false, CoolingType::CPU,
+                                                           &cooling_devices)) {
+                dump_buf << " Failed to getCurrentCoolingDevices." << std::endl;
+            }
+
+            for (const auto &c : cooling_devices) {
+                dump_buf << " Type: " << toString(c.type) << " Name: " << c.name
+                         << " CurrentValue: " << c.value << std::endl;
+            }
+        }
+        {
+            dump_buf << "getCallbacks:" << std::endl;
+            dump_buf << " Total: " << callbacks_.size() << std::endl;
+            for (const auto &c : callbacks_) {
+                dump_buf << " IsFilter: " << c.is_filter_type << " Type: " << toString(c.type)
+                         << std::endl;
+            }
+        }
+        {
+            dump_buf << "sendCallback:" << std::endl;
+            dump_buf << "  Enabled List: ";
+            const auto &map = thermal_helper_.GetSensorInfoMap();
+            for (const auto &name_info_pair : map) {
+                if (name_info_pair.second.send_cb) {
+                    dump_buf << name_info_pair.first << " ";
+                }
+            }
+            dump_buf << std::endl;
+        }
+        {
+            dump_buf << "sendPowerHint:" << std::endl;
+            dump_buf << "  Enabled List: ";
+            const auto &map = thermal_helper_.GetSensorInfoMap();
+            for (const auto &name_info_pair : map) {
+                if (name_info_pair.second.send_powerhint) {
+                    dump_buf << name_info_pair.first << " ";
+                }
+            }
+            dump_buf << std::endl;
+        }
+        dumpVirtualSensorInfo(&dump_buf);
+        dumpThrottlingInfo(&dump_buf);
+        dumpThrottlingRequestStatus(&dump_buf);
+        dumpPowerRailInfo(&dump_buf);
+        dumpThermalStats(&dump_buf);
+        {
+            dump_buf << "getAIDLPowerHalInfo:" << std::endl;
+            dump_buf << " Exist: " << std::boolalpha << thermal_helper_.isAidlPowerHalExist()
+                     << std::endl;
+            dump_buf << " Connected: " << std::boolalpha << thermal_helper_.isPowerHalConnected()
+                     << std::endl;
+            dump_buf << " Ext connected: " << std::boolalpha
+                     << thermal_helper_.isPowerHalExtConnected() << std::endl;
+        }
+    }
+    std::string buf = dump_buf.str();
+    if (!::android::base::WriteStringToFd(buf, fd)) {
+        PLOG(ERROR) << "Failed to dump state to fd";
+    }
+    fsync(fd);
+}
+
+binder_status_t Thermal::dump(int fd, const char **args, uint32_t numArgs) {
+    if (numArgs == 0 || std::string(args[0]) == "-a") {
+        dumpThermalData(fd);
+        return STATUS_OK;
+    }
+
+    if (std::string(args[0]) == "emul_temp") {
+        return (numArgs != 3 || !thermal_helper_.emulTemp(std::string(args[1]), std::atof(args[2])))
+                       ? STATUS_BAD_VALUE
+                       : STATUS_OK;
+    } else if (std::string(args[0]) == "emul_severity") {
+        return (numArgs != 3 ||
+                !thermal_helper_.emulSeverity(std::string(args[1]), std::atoi(args[2])))
+                       ? STATUS_BAD_VALUE
+                       : STATUS_OK;
+    } else if (std::string(args[0]) == "emul_clear") {
+        return (numArgs != 2 || !thermal_helper_.emulClear(std::string(args[1]))) ? STATUS_BAD_VALUE
+                                                                                  : STATUS_OK;
+    }
+    return STATUS_BAD_VALUE;
+}
+
+void Thermal::Looper::addEvent(const Thermal::Looper::Event &e) {
+    std::unique_lock<std::mutex> lock(mutex_);
+    events_.push(e);
+    cv_.notify_all();
+}
+
+void Thermal::Looper::loop() {
+    while (true) {
+        std::unique_lock<std::mutex> lock(mutex_);
+        cv_.wait(lock, [&] { return !events_.empty(); });
+        Event event = events_.front();
+        events_.pop();
+        lock.unlock();
+        event.handler();
+    }
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/Thermal.h b/aidl/thermal/Thermal.h
new file mode 100644 (file)
index 0000000..74449c4
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/thermal/BnThermal.h>
+
+#include <mutex>
+#include <thread>
+
+#include "thermal-helper.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+struct CallbackSetting {
+    CallbackSetting(std::shared_ptr<IThermalChangedCallback> callback, bool is_filter_type,
+                    TemperatureType type)
+        : callback(std::move(callback)), is_filter_type(is_filter_type), type(type) {}
+    std::shared_ptr<IThermalChangedCallback> callback;
+    bool is_filter_type;
+    TemperatureType type;
+};
+
+class Thermal : public BnThermal {
+  public:
+    Thermal();
+    ~Thermal() = default;
+    ndk::ScopedAStatus getTemperatures(std::vector<Temperature> *_aidl_return) override;
+    ndk::ScopedAStatus getTemperaturesWithType(TemperatureType type,
+                                               std::vector<Temperature> *_aidl_return) override;
+
+    ndk::ScopedAStatus getCoolingDevices(std::vector<CoolingDevice> *_aidl_return) override;
+    ndk::ScopedAStatus getCoolingDevicesWithType(CoolingType type,
+                                                 std::vector<CoolingDevice> *_aidl_return) override;
+
+    ndk::ScopedAStatus getTemperatureThresholds(
+            std::vector<TemperatureThreshold> *_aidl_return) override;
+    ndk::ScopedAStatus getTemperatureThresholdsWithType(
+            TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return) override;
+
+    ndk::ScopedAStatus registerThermalChangedCallback(
+            const std::shared_ptr<IThermalChangedCallback> &callback) override;
+    ndk::ScopedAStatus registerThermalChangedCallbackWithType(
+            const std::shared_ptr<IThermalChangedCallback> &callback,
+            TemperatureType type) override;
+    ndk::ScopedAStatus unregisterThermalChangedCallback(
+            const std::shared_ptr<IThermalChangedCallback> &callback) override;
+    binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
+
+    // Helper function for calling callbacks
+    void sendThermalChangedCallback(const Temperature &t);
+
+  private:
+    class Looper {
+      public:
+        struct Event {
+            std::function<void()> handler;
+        };
+
+        Looper() {
+            thread_ = std::thread([&] { loop(); });
+        }
+        void addEvent(const Event &e);
+
+      private:
+        std::condition_variable cv_;
+        std::queue<Event> events_;
+        std::mutex mutex_;
+        std::thread thread_;
+
+        void loop();
+    };
+
+    ThermalHelper thermal_helper_;
+    std::mutex thermal_callback_mutex_;
+    std::vector<CallbackSetting> callbacks_;
+    Looper looper_;
+
+    ndk::ScopedAStatus getFilteredTemperatures(bool filterType, TemperatureType type,
+                                               std::vector<Temperature> *_aidl_return);
+    ndk::ScopedAStatus getFilteredCoolingDevices(bool filterType, CoolingType type,
+                                                 std::vector<CoolingDevice> *_aidl_return);
+    ndk::ScopedAStatus getFilteredTemperatureThresholds(
+            bool filterType, TemperatureType type, std::vector<TemperatureThreshold> *_aidl_return);
+    ndk::ScopedAStatus registerThermalChangedCallback(
+            const std::shared_ptr<IThermalChangedCallback> &callback, bool filterType,
+            TemperatureType type);
+
+    void dumpVirtualSensorInfo(std::ostringstream *dump_buf);
+    void dumpThrottlingInfo(std::ostringstream *dump_buf);
+    void dumpThrottlingRequestStatus(std::ostringstream *dump_buf);
+    void dumpPowerRailInfo(std::ostringstream *dump_buf);
+    void dumpStatsRecord(std::ostringstream *dump_buf, const StatsRecord &stats_record,
+                         std::string_view line_prefix);
+    void dumpThermalStats(std::ostringstream *dump_buf);
+    void dumpThermalData(int fd);
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/android.hardware.thermal-service.pixel.rc b/aidl/thermal/android.hardware.thermal-service.pixel.rc
new file mode 100644 (file)
index 0000000..f9f823b
--- /dev/null
@@ -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 (file)
index 0000000..bdee744
--- /dev/null
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.thermal</name>
+        <version>1</version>
+        <fqname>IThermal/default</fqname>
+    </hal>
+</manifest>
diff --git a/aidl/thermal/init.thermal.logging.sh b/aidl/thermal/init.thermal.logging.sh
new file mode 100755 (executable)
index 0000000..de385ab
--- /dev/null
@@ -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 (executable)
index 0000000..d990897
--- /dev/null
@@ -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 (file)
index 0000000..c2ec9ff
--- /dev/null
@@ -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 (file)
index 0000000..132ec5f
--- /dev/null
@@ -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 (file)
index 0000000..d18a069
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Thermal.h"
+
+constexpr std::string_view kThermalLogTag("pixel-thermal");
+
+using ::android::OK;
+using ::android::status_t;
+
+// Generated AIDL files:
+using Thermal = ::aidl::android::hardware::thermal::implementation::Thermal;
+
+#if !defined(THERMAL_INSTANCE_NAME)
+#define THERMAL_INSTANCE_NAME "default"
+#endif
+
+int main(int /* argc */, char ** /* argv */) {
+    android::base::SetDefaultTag(kThermalLogTag.data());
+
+    auto svc = ndk::SharedRefBase::make<Thermal>();
+    const auto svcName = std::string() + svc->descriptor + "/" + THERMAL_INSTANCE_NAME;
+    LOG(INFO) << "Pixel Thermal AIDL Service starting..." + svcName;
+    ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+    auto svcBinder = svc->asBinder();
+    binder_status_t status = AServiceManager_addService(svcBinder.get(), svcName.c_str());
+    if (status != STATUS_OK) {
+        LOG(ERROR) << "Pixel Thermal AIDL Service failed to start: " << status << ".";
+        return EXIT_FAILURE;
+    }
+    LOG(INFO) << "Pixel Thermal HAL AIDL Service started.";
+    ABinderProcess_joinThreadPool();
+    return EXIT_FAILURE;  // should not reach
+}
diff --git a/aidl/thermal/thermal-helper.cpp b/aidl/thermal/thermal-helper.cpp
new file mode 100644 (file)
index 0000000..d7411bc
--- /dev/null
@@ -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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <utils/Trace.h>
+
+#include <iterator>
+#include <set>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr std::string_view kThermalSensorsRoot("/sys/devices/virtual/thermal");
+constexpr std::string_view kSensorPrefix("thermal_zone");
+constexpr std::string_view kCoolingDevicePrefix("cooling_device");
+constexpr std::string_view kThermalNameFile("type");
+constexpr std::string_view kSensorPolicyFile("policy");
+constexpr std::string_view kSensorTempSuffix("temp");
+constexpr std::string_view kSensorTripPointTempZeroFile("trip_point_0_temp");
+constexpr std::string_view kSensorTripPointHystZeroFile("trip_point_0_hyst");
+constexpr std::string_view kUserSpaceSuffix("user_space");
+constexpr std::string_view kCoolingDeviceCurStateSuffix("cur_state");
+constexpr std::string_view kCoolingDeviceMaxStateSuffix("max_state");
+constexpr std::string_view kCoolingDeviceState2powerSuffix("state2power_table");
+constexpr std::string_view kConfigProperty("vendor.thermal.config");
+constexpr std::string_view kConfigDefaultFileName("thermal_info_config.json");
+constexpr std::string_view kThermalGenlProperty("persist.vendor.enable.thermal.genl");
+constexpr std::string_view kThermalDisabledProperty("vendor.disable.thermalhal.control");
+
+namespace {
+using ::android::base::StringPrintf;
+
+std::unordered_map<std::string, std::string> parseThermalPathMap(std::string_view prefix) {
+    std::unordered_map<std::string, std::string> path_map;
+    std::unique_ptr<DIR, int (*)(DIR *)> dir(opendir(kThermalSensorsRoot.data()), closedir);
+    if (!dir) {
+        return path_map;
+    }
+
+    // std::filesystem is not available for vendor yet
+    // see discussion: aosp/894015
+    while (struct dirent *dp = readdir(dir.get())) {
+        if (dp->d_type != DT_DIR) {
+            continue;
+        }
+
+        if (!::android::base::StartsWith(dp->d_name, prefix.data())) {
+            continue;
+        }
+
+        std::string path = ::android::base::StringPrintf("%s/%s/%s", kThermalSensorsRoot.data(),
+                                                         dp->d_name, kThermalNameFile.data());
+        std::string name;
+        if (!::android::base::ReadFileToString(path, &name)) {
+            PLOG(ERROR) << "Failed to read from " << path;
+            continue;
+        }
+
+        path_map.emplace(
+                ::android::base::Trim(name),
+                ::android::base::StringPrintf("%s/%s", kThermalSensorsRoot.data(), dp->d_name));
+    }
+
+    return path_map;
+}
+
+}  // namespace
+
+/*
+ * Populate the sensor_name_to_file_map_ map by walking through the file tree,
+ * reading the type file and assigning the temp file path to the map.  If we do
+ * not succeed, abort.
+ */
+ThermalHelper::ThermalHelper(const NotificationCallback &cb)
+    : thermal_watcher_(new ThermalWatcher(
+              std::bind(&ThermalHelper::thermalWatcherCallbackFunc, this, std::placeholders::_1))),
+      cb_(cb) {
+    const std::string config_path =
+            "/vendor/etc/" +
+            ::android::base::GetProperty(kConfigProperty.data(), kConfigDefaultFileName.data());
+    bool thermal_throttling_disabled =
+            ::android::base::GetBoolProperty(kThermalDisabledProperty.data(), false);
+    bool ret = true;
+    Json::Value config;
+    if (!ParseThermalConfig(config_path, &config)) {
+        LOG(ERROR) << "Failed to read JSON config";
+        ret = false;
+    }
+
+    if (!ParseCoolingDevice(config, &cooling_device_info_map_)) {
+        LOG(ERROR) << "Failed to parse cooling device info config";
+        ret = false;
+    }
+
+    if (!ParseSensorInfo(config, &sensor_info_map_)) {
+        LOG(ERROR) << "Failed to parse sensor info config";
+        ret = false;
+    }
+
+    auto tz_map = parseThermalPathMap(kSensorPrefix.data());
+    if (!initializeSensorMap(tz_map)) {
+        LOG(ERROR) << "Failed to initialize sensor map";
+        ret = false;
+    }
+
+    auto cdev_map = parseThermalPathMap(kCoolingDevicePrefix.data());
+    if (!initializeCoolingDevices(cdev_map)) {
+        LOG(ERROR) << "Failed to initialize cooling device map";
+        ret = false;
+    }
+
+    if (!power_files_.registerPowerRailsToWatch(config)) {
+        LOG(ERROR) << "Failed to register power rails";
+        ret = false;
+    }
+
+    if (!thermal_stats_helper_.initializeStats(config, sensor_info_map_,
+                                               cooling_device_info_map_)) {
+        LOG(FATAL) << "Failed to initialize thermal stats";
+    }
+
+    for (auto const &name_status_pair : sensor_info_map_) {
+        sensor_status_map_[name_status_pair.first] = {
+                .severity = ThrottlingSeverity::NONE,
+                .prev_hot_severity = ThrottlingSeverity::NONE,
+                .prev_cold_severity = ThrottlingSeverity::NONE,
+                .prev_hint_severity = ThrottlingSeverity::NONE,
+                .last_update_time = boot_clock::time_point::min(),
+                .thermal_cached = {NAN, boot_clock::time_point::min()},
+                .emul_setting = nullptr,
+        };
+
+        if (name_status_pair.second.throttling_info != nullptr) {
+            if (!thermal_throttling_.registerThermalThrottling(
+                        name_status_pair.first, name_status_pair.second.throttling_info,
+                        cooling_device_info_map_)) {
+                LOG(ERROR) << name_status_pair.first << " failed to register thermal throttling";
+                ret = false;
+                break;
+            }
+
+            // Update cooling device max state
+            for (auto &binded_cdev_info_pair :
+                 name_status_pair.second.throttling_info->binded_cdev_info_map) {
+                const auto &cdev_info = cooling_device_info_map_.at(binded_cdev_info_pair.first);
+
+                for (auto &cdev_ceiling : binded_cdev_info_pair.second.cdev_ceiling) {
+                    if (cdev_ceiling > cdev_info.max_state) {
+                        if (cdev_ceiling != std::numeric_limits<int>::max()) {
+                            LOG(WARNING) << "Sensor " << name_status_pair.first << "'s "
+                                         << binded_cdev_info_pair.first
+                                         << " cdev_ceiling:" << cdev_ceiling
+                                         << " is higher than max state:" << cdev_info.max_state;
+                        }
+                        cdev_ceiling = cdev_info.max_state;
+                    }
+                }
+            }
+        }
+        // Check the virtual sensor settings are valid
+        if (name_status_pair.second.virtual_sensor_info != nullptr) {
+            // Check if sub sensor setting is valid
+            for (size_t i = 0;
+                 i < name_status_pair.second.virtual_sensor_info->linked_sensors.size(); i++) {
+                if (!isSubSensorValid(
+                            name_status_pair.second.virtual_sensor_info->linked_sensors[i],
+                            name_status_pair.second.virtual_sensor_info->linked_sensors_type[i])) {
+                    LOG(ERROR) << name_status_pair.first << "'s link sensor "
+                               << name_status_pair.second.virtual_sensor_info->linked_sensors[i]
+                               << " is invalid";
+                    ret = false;
+                    break;
+                }
+            }
+
+            // Check if the trigger sensor is valid
+            if (!name_status_pair.second.virtual_sensor_info->trigger_sensors.empty() &&
+                name_status_pair.second.is_watch) {
+                for (size_t i = 0;
+                     i < name_status_pair.second.virtual_sensor_info->trigger_sensors.size(); i++) {
+                    if (sensor_info_map_.count(
+                                name_status_pair.second.virtual_sensor_info->trigger_sensors[i])) {
+                        sensor_info_map_[name_status_pair.second.virtual_sensor_info
+                                                 ->trigger_sensors[i]]
+                                .is_watch = true;
+                    } else {
+                        LOG(ERROR)
+                                << name_status_pair.first << "'s trigger sensor: "
+                                << name_status_pair.second.virtual_sensor_info->trigger_sensors[i]
+                                << " is invalid";
+                        ret = false;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    if (!connectToPowerHal()) {
+        LOG(ERROR) << "Fail to connect to Power Hal";
+    } else {
+        updateSupportedPowerHints();
+    }
+
+    if (thermal_throttling_disabled) {
+        if (ret) {
+            clearAllThrottling();
+            is_initialized_ = ret;
+            return;
+        } else {
+            sensor_info_map_.clear();
+            cooling_device_info_map_.clear();
+            return;
+        }
+    } else if (!ret) {
+        LOG(FATAL) << "ThermalHAL could not be initialized properly.";
+    }
+    is_initialized_ = ret;
+
+    const bool thermal_genl_enabled =
+            ::android::base::GetBoolProperty(kThermalGenlProperty.data(), false);
+
+    std::set<std::string> monitored_sensors;
+    initializeTrip(tz_map, &monitored_sensors, thermal_genl_enabled);
+
+    if (thermal_genl_enabled) {
+        thermal_watcher_->registerFilesToWatchNl(monitored_sensors);
+    } else {
+        thermal_watcher_->registerFilesToWatch(monitored_sensors);
+    }
+
+    // Need start watching after status map initialized
+    is_initialized_ = thermal_watcher_->startWatchingDeviceFiles();
+    if (!is_initialized_) {
+        LOG(FATAL) << "ThermalHAL could not start watching thread properly.";
+    }
+
+    if (!connectToPowerHal()) {
+        LOG(ERROR) << "Fail to connect to Power Hal";
+    } else {
+        updateSupportedPowerHints();
+    }
+}
+
+bool getThermalZoneTypeById(int tz_id, std::string *type) {
+    std::string tz_type;
+    std::string path =
+            ::android::base::StringPrintf("%s/%s%d/%s", kThermalSensorsRoot.data(),
+                                          kSensorPrefix.data(), tz_id, kThermalNameFile.data());
+    LOG(INFO) << "TZ Path: " << path;
+    if (!::android::base::ReadFileToString(path, &tz_type)) {
+        LOG(ERROR) << "Failed to read sensor: " << tz_type;
+        return false;
+    }
+
+    // Strip the newline.
+    *type = ::android::base::Trim(tz_type);
+    LOG(INFO) << "TZ type: " << *type;
+    return true;
+}
+
+bool ThermalHelper::emulTemp(std::string_view target_sensor, const float value) {
+    LOG(INFO) << "Set " << target_sensor.data() << " emul_temp "
+              << "to " << value;
+
+    std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+    // Check the target sensor is valid
+    if (!sensor_status_map_.count(target_sensor.data())) {
+        LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
+        return false;
+    }
+
+    sensor_status_map_.at(target_sensor.data())
+            .emul_setting.reset(new EmulSetting{value, -1, true});
+
+    thermal_watcher_->wake();
+    return true;
+}
+
+bool ThermalHelper::emulSeverity(std::string_view target_sensor, const int severity) {
+    LOG(INFO) << "Set " << target_sensor.data() << " emul_severity "
+              << "to " << severity;
+
+    std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+    // Check the target sensor is valid
+    if (!sensor_status_map_.count(target_sensor.data())) {
+        LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
+        return false;
+    }
+    // Check the emul severity is valid
+    if (severity > static_cast<int>(kThrottlingSeverityCount)) {
+        LOG(ERROR) << "Invalid emul severity value " << severity;
+        return false;
+    }
+
+    sensor_status_map_.at(target_sensor.data())
+            .emul_setting.reset(new EmulSetting{NAN, severity, true});
+
+    thermal_watcher_->wake();
+    return true;
+}
+
+bool ThermalHelper::emulClear(std::string_view target_sensor) {
+    LOG(INFO) << "Clear " << target_sensor.data() << " emulation settings";
+
+    std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+    if (target_sensor == "all") {
+        for (auto &sensor_status : sensor_status_map_) {
+            if (sensor_status.second.emul_setting != nullptr) {
+                sensor_status.second.emul_setting.reset(new EmulSetting{NAN, -1, true});
+            }
+        }
+    } else if (sensor_status_map_.count(target_sensor.data()) &&
+               sensor_status_map_.at(target_sensor.data()).emul_setting != nullptr) {
+        sensor_status_map_.at(target_sensor.data())
+                .emul_setting.reset(new EmulSetting{NAN, -1, true});
+    } else {
+        LOG(ERROR) << "Cannot find target emul sensor: " << target_sensor.data();
+        return false;
+    }
+    return true;
+}
+
+bool ThermalHelper::readCoolingDevice(std::string_view cooling_device, CoolingDevice *out) const {
+    // Read the file.  If the file can't be read temp will be empty string.
+    std::string data;
+
+    if (!cooling_devices_.readThermalFile(cooling_device, &data)) {
+        LOG(ERROR) << "readCoolingDevice: failed to read cooling_device: " << cooling_device;
+        return false;
+    }
+
+    const CdevInfo &cdev_info = cooling_device_info_map_.at(cooling_device.data());
+    const CoolingType &type = cdev_info.type;
+
+    out->type = type;
+    out->name = cooling_device.data();
+    out->value = std::stoi(data);
+
+    return true;
+}
+
+bool ThermalHelper::readTemperature(
+        std::string_view sensor_name, Temperature *out,
+        std::pair<ThrottlingSeverity, ThrottlingSeverity> *throttling_status,
+        const bool force_no_cache) {
+    // Return fail if the thermal sensor cannot be read.
+    float temp;
+    std::map<std::string, float> sensor_log_map;
+    auto &sensor_status = sensor_status_map_.at(sensor_name.data());
+
+    if (!readThermalSensor(sensor_name, &temp, force_no_cache, &sensor_log_map)) {
+        LOG(ERROR) << "readTemperature: failed to read sensor: " << sensor_name;
+        return false;
+    }
+
+    const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+    out->type = sensor_info.type;
+    out->name = sensor_name.data();
+    out->value = temp * sensor_info.multiplier;
+
+    std::pair<ThrottlingSeverity, ThrottlingSeverity> status =
+            std::make_pair(ThrottlingSeverity::NONE, ThrottlingSeverity::NONE);
+    // Only update status if the thermal sensor is being monitored
+    if (sensor_info.is_watch) {
+        ThrottlingSeverity prev_hot_severity, prev_cold_severity;
+        {
+            // reader lock, readTemperature will be called in Binder call and the watcher thread.
+            std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+            prev_hot_severity = sensor_status.prev_hot_severity;
+            prev_cold_severity = sensor_status.prev_cold_severity;
+        }
+        status = getSeverityFromThresholds(sensor_info.hot_thresholds, sensor_info.cold_thresholds,
+                                           sensor_info.hot_hysteresis, sensor_info.cold_hysteresis,
+                                           prev_hot_severity, prev_cold_severity, out->value);
+    }
+
+    if (throttling_status) {
+        *throttling_status = status;
+    }
+
+    if (sensor_status.emul_setting != nullptr && sensor_status.emul_setting->emul_severity >= 0) {
+        std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+        out->throttlingStatus =
+                static_cast<ThrottlingSeverity>(sensor_status.emul_setting->emul_severity);
+    } else {
+        out->throttlingStatus =
+                static_cast<size_t>(status.first) > static_cast<size_t>(status.second)
+                        ? status.first
+                        : status.second;
+    }
+    if (sensor_info.is_watch) {
+        std::ostringstream sensor_log;
+        for (const auto &sensor_log_pair : sensor_log_map) {
+            sensor_log << sensor_log_pair.first << ":" << sensor_log_pair.second << " ";
+        }
+        // Update sensor temperature time in state
+        thermal_stats_helper_.updateSensorTempStatsBySeverity(sensor_name, out->throttlingStatus);
+        LOG(INFO) << sensor_name.data() << ":" << out->value << " raw data: " << sensor_log.str();
+    }
+
+    return true;
+}
+
+bool ThermalHelper::readTemperatureThreshold(std::string_view sensor_name,
+                                             TemperatureThreshold *out) const {
+    // Read the file.  If the file can't be read temp will be empty string.
+    std::string temp;
+    std::string path;
+
+    if (!sensor_info_map_.count(sensor_name.data())) {
+        LOG(ERROR) << __func__ << ": sensor not found: " << sensor_name;
+        return false;
+    }
+
+    const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+
+    out->type = sensor_info.type;
+    out->name = sensor_name.data();
+    out->hotThrottlingThresholds =
+            std::vector(sensor_info.hot_thresholds.begin(), sensor_info.hot_thresholds.end());
+    out->coldThrottlingThresholds =
+            std::vector(sensor_info.cold_thresholds.begin(), sensor_info.cold_thresholds.end());
+    return true;
+}
+
+void ThermalHelper::updateCoolingDevices(const std::vector<std::string> &updated_cdev) {
+    int max_state;
+
+    for (const auto &target_cdev : updated_cdev) {
+        if (thermal_throttling_.getCdevMaxRequest(target_cdev, &max_state)) {
+            if (cooling_devices_.writeCdevFile(target_cdev, std::to_string(max_state))) {
+                ATRACE_INT(target_cdev.c_str(), max_state);
+                LOG(INFO) << "Successfully update cdev " << target_cdev << " sysfs to "
+                          << max_state;
+            } else {
+                LOG(ERROR) << "Failed to update cdev " << target_cdev << " sysfs to " << max_state;
+            }
+        }
+    }
+}
+
+std::pair<ThrottlingSeverity, ThrottlingSeverity> ThermalHelper::getSeverityFromThresholds(
+        const ThrottlingArray &hot_thresholds, const ThrottlingArray &cold_thresholds,
+        const ThrottlingArray &hot_hysteresis, const ThrottlingArray &cold_hysteresis,
+        ThrottlingSeverity prev_hot_severity, ThrottlingSeverity prev_cold_severity,
+        float value) const {
+    ThrottlingSeverity ret_hot = ThrottlingSeverity::NONE;
+    ThrottlingSeverity ret_hot_hysteresis = ThrottlingSeverity::NONE;
+    ThrottlingSeverity ret_cold = ThrottlingSeverity::NONE;
+    ThrottlingSeverity ret_cold_hysteresis = ThrottlingSeverity::NONE;
+
+    // Here we want to control the iteration from high to low, and ::ndk::enum_range doesn't support
+    // a reverse iterator yet.
+    for (size_t i = static_cast<size_t>(ThrottlingSeverity::SHUTDOWN);
+         i > static_cast<size_t>(ThrottlingSeverity::NONE); --i) {
+        if (!std::isnan(hot_thresholds[i]) && hot_thresholds[i] <= value &&
+            ret_hot == ThrottlingSeverity::NONE) {
+            ret_hot = static_cast<ThrottlingSeverity>(i);
+        }
+        if (!std::isnan(hot_thresholds[i]) && (hot_thresholds[i] - hot_hysteresis[i]) < value &&
+            ret_hot_hysteresis == ThrottlingSeverity::NONE) {
+            ret_hot_hysteresis = static_cast<ThrottlingSeverity>(i);
+        }
+        if (!std::isnan(cold_thresholds[i]) && cold_thresholds[i] >= value &&
+            ret_cold == ThrottlingSeverity::NONE) {
+            ret_cold = static_cast<ThrottlingSeverity>(i);
+        }
+        if (!std::isnan(cold_thresholds[i]) && (cold_thresholds[i] + cold_hysteresis[i]) > value &&
+            ret_cold_hysteresis == ThrottlingSeverity::NONE) {
+            ret_cold_hysteresis = static_cast<ThrottlingSeverity>(i);
+        }
+    }
+    if (static_cast<size_t>(ret_hot) < static_cast<size_t>(prev_hot_severity)) {
+        ret_hot = ret_hot_hysteresis;
+    }
+    if (static_cast<size_t>(ret_cold) < static_cast<size_t>(prev_cold_severity)) {
+        ret_cold = ret_cold_hysteresis;
+    }
+
+    return std::make_pair(ret_hot, ret_cold);
+}
+
+bool ThermalHelper::isSubSensorValid(std::string_view sensor_data,
+                                     const SensorFusionType sensor_fusion_type) {
+    switch (sensor_fusion_type) {
+        case SensorFusionType::SENSOR:
+            if (!sensor_info_map_.count(sensor_data.data())) {
+                LOG(ERROR) << "Cannot find " << sensor_data.data() << " from sensor info map";
+                return false;
+            }
+            break;
+        case SensorFusionType::ODPM:
+            if (!GetPowerStatusMap().count(sensor_data.data())) {
+                LOG(ERROR) << "Cannot find " << sensor_data.data() << " from power status map";
+                return false;
+            }
+            break;
+        default:
+            break;
+    }
+    return true;
+}
+
+void ThermalHelper::clearAllThrottling(void) {
+    // Clear the CDEV request
+    for (const auto &cdev_info_pair : cooling_device_info_map_) {
+        cooling_devices_.writeCdevFile(cdev_info_pair.first, "0");
+    }
+
+    for (auto &sensor_info_pair : sensor_info_map_) {
+        sensor_info_pair.second.is_watch = false;
+        sensor_info_pair.second.throttling_info.reset();
+        sensor_info_pair.second.hot_thresholds.fill(NAN);
+        sensor_info_pair.second.cold_thresholds.fill(NAN);
+        Temperature temp = {
+                .type = sensor_info_pair.second.type,
+                .name = sensor_info_pair.first,
+                .value = NAN,
+                .throttlingStatus = ThrottlingSeverity::NONE,
+        };
+        // Send callbacks with NONE severity
+        if (sensor_info_pair.second.send_cb && cb_) {
+            cb_(temp);
+        }
+        // Disable thermal power hints
+        if (sensor_info_pair.second.send_powerhint) {
+            for (const auto &severity : ::ndk::enum_range<ThrottlingSeverity>()) {
+                power_hal_service_.setMode(sensor_info_pair.first, severity, false);
+            }
+        }
+    }
+}
+
+bool ThermalHelper::initializeSensorMap(
+        const std::unordered_map<std::string, std::string> &path_map) {
+    for (const auto &sensor_info_pair : sensor_info_map_) {
+        std::string_view sensor_name = sensor_info_pair.first;
+        if (sensor_info_pair.second.virtual_sensor_info != nullptr) {
+            continue;
+        }
+        if (!path_map.count(sensor_name.data())) {
+            LOG(ERROR) << "Could not find " << sensor_name << " in sysfs";
+            return false;
+        }
+
+        std::string path;
+        if (sensor_info_pair.second.temp_path.empty()) {
+            path = ::android::base::StringPrintf("%s/%s", path_map.at(sensor_name.data()).c_str(),
+                                                 kSensorTempSuffix.data());
+        } else {
+            path = sensor_info_pair.second.temp_path;
+        }
+
+        if (!thermal_sensors_.addThermalFile(sensor_name, path)) {
+            LOG(ERROR) << "Could not add " << sensor_name << "to sensors map";
+            return false;
+        }
+    }
+    return true;
+}
+
+bool ThermalHelper::initializeCoolingDevices(
+        const std::unordered_map<std::string, std::string> &path_map) {
+    for (auto &cooling_device_info_pair : cooling_device_info_map_) {
+        std::string cooling_device_name = cooling_device_info_pair.first;
+        if (!path_map.count(cooling_device_name)) {
+            LOG(ERROR) << "Could not find " << cooling_device_name << " in sysfs";
+            return false;
+        }
+        // Add cooling device path for thermalHAL to get current state
+        std::string_view path = path_map.at(cooling_device_name);
+        std::string read_path;
+        if (!cooling_device_info_pair.second.read_path.empty()) {
+            read_path = cooling_device_info_pair.second.read_path.data();
+        } else {
+            read_path = ::android::base::StringPrintf("%s/%s", path.data(),
+                                                      kCoolingDeviceCurStateSuffix.data());
+        }
+        if (!cooling_devices_.addThermalFile(cooling_device_name, read_path)) {
+            LOG(ERROR) << "Could not add " << cooling_device_name
+                       << " read path to cooling device map";
+            return false;
+        }
+
+        std::string state2power_path = ::android::base::StringPrintf(
+                "%s/%s", path.data(), kCoolingDeviceState2powerSuffix.data());
+        std::string state2power_str;
+        if (::android::base::ReadFileToString(state2power_path, &state2power_str)) {
+            LOG(INFO) << "Cooling device " << cooling_device_info_pair.first
+                      << " use state2power read from sysfs";
+            cooling_device_info_pair.second.state2power.clear();
+
+            std::stringstream power(state2power_str);
+            unsigned int power_number;
+            int i = 0;
+            while (power >> power_number) {
+                cooling_device_info_pair.second.state2power.push_back(
+                        static_cast<float>(power_number));
+                LOG(INFO) << "Cooling device " << cooling_device_info_pair.first << " state:" << i
+                          << " power: " << power_number;
+                i++;
+            }
+        }
+
+        // Get max cooling device request state
+        std::string max_state;
+        std::string max_state_path = ::android::base::StringPrintf(
+                "%s/%s", path.data(), kCoolingDeviceMaxStateSuffix.data());
+        if (!::android::base::ReadFileToString(max_state_path, &max_state)) {
+            LOG(ERROR) << cooling_device_info_pair.first
+                       << " could not open max state file:" << max_state_path;
+            cooling_device_info_pair.second.max_state = std::numeric_limits<int>::max();
+        } else {
+            cooling_device_info_pair.second.max_state = std::stoi(::android::base::Trim(max_state));
+            LOG(INFO) << "Cooling device " << cooling_device_info_pair.first
+                      << " max state: " << cooling_device_info_pair.second.max_state
+                      << " state2power number: "
+                      << cooling_device_info_pair.second.state2power.size();
+            if (cooling_device_info_pair.second.state2power.size() > 0 &&
+                static_cast<int>(cooling_device_info_pair.second.state2power.size()) !=
+                        (cooling_device_info_pair.second.max_state + 1)) {
+                LOG(ERROR) << "Invalid state2power number: "
+                           << cooling_device_info_pair.second.state2power.size()
+                           << ", number should be " << cooling_device_info_pair.second.max_state + 1
+                           << " (max_state + 1)";
+                return false;
+            }
+        }
+
+        // Add cooling device path for thermalHAL to request state
+        cooling_device_name =
+                ::android::base::StringPrintf("%s_%s", cooling_device_name.c_str(), "w");
+        std::string write_path;
+        if (!cooling_device_info_pair.second.write_path.empty()) {
+            write_path = cooling_device_info_pair.second.write_path.data();
+        } else {
+            write_path = ::android::base::StringPrintf("%s/%s", path.data(),
+                                                       kCoolingDeviceCurStateSuffix.data());
+        }
+
+        if (!cooling_devices_.addThermalFile(cooling_device_name, write_path)) {
+            LOG(ERROR) << "Could not add " << cooling_device_name
+                       << " write path to cooling device map";
+            return false;
+        }
+    }
+    return true;
+}
+
+void ThermalHelper::setMinTimeout(SensorInfo *sensor_info) {
+    sensor_info->polling_delay = kMinPollIntervalMs;
+    sensor_info->passive_delay = kMinPollIntervalMs;
+}
+
+void ThermalHelper::initializeTrip(const std::unordered_map<std::string, std::string> &path_map,
+                                   std::set<std::string> *monitored_sensors,
+                                   bool thermal_genl_enabled) {
+    for (auto &sensor_info : sensor_info_map_) {
+        if (!sensor_info.second.is_watch || (sensor_info.second.virtual_sensor_info != nullptr)) {
+            continue;
+        }
+
+        bool trip_update = false;
+        std::string_view sensor_name = sensor_info.first;
+        std::string_view tz_path = path_map.at(sensor_name.data());
+        std::string tz_policy;
+        std::string path =
+                ::android::base::StringPrintf("%s/%s", (tz_path.data()), kSensorPolicyFile.data());
+
+        if (thermal_genl_enabled) {
+            trip_update = true;
+        } else {
+            // Check if thermal zone support uevent notify
+            if (!::android::base::ReadFileToString(path, &tz_policy)) {
+                LOG(ERROR) << sensor_name << " could not open tz policy file:" << path;
+            } else {
+                tz_policy = ::android::base::Trim(tz_policy);
+                if (tz_policy != kUserSpaceSuffix) {
+                    LOG(ERROR) << sensor_name << " does not support uevent notify";
+                } else {
+                    trip_update = true;
+                }
+            }
+        }
+        if (trip_update) {
+            // Update thermal zone trip point
+            for (size_t i = 0; i < kThrottlingSeverityCount; ++i) {
+                if (!std::isnan(sensor_info.second.hot_thresholds[i]) &&
+                    !std::isnan(sensor_info.second.hot_hysteresis[i])) {
+                    // Update trip_point_0_temp threshold
+                    std::string threshold = std::to_string(static_cast<int>(
+                            sensor_info.second.hot_thresholds[i] / sensor_info.second.multiplier));
+                    path = ::android::base::StringPrintf("%s/%s", (tz_path.data()),
+                                                         kSensorTripPointTempZeroFile.data());
+                    if (!::android::base::WriteStringToFile(threshold, path)) {
+                        LOG(ERROR) << "fail to update " << sensor_name << " trip point: " << path
+                                   << " to " << threshold;
+                        trip_update = false;
+                        break;
+                    }
+                    // Update trip_point_0_hyst threshold
+                    threshold = std::to_string(static_cast<int>(
+                            sensor_info.second.hot_hysteresis[i] / sensor_info.second.multiplier));
+                    path = ::android::base::StringPrintf("%s/%s", (tz_path.data()),
+                                                         kSensorTripPointHystZeroFile.data());
+                    if (!::android::base::WriteStringToFile(threshold, path)) {
+                        LOG(ERROR) << "fail to update " << sensor_name << "trip hyst" << threshold
+                                   << path;
+                        trip_update = false;
+                        break;
+                    }
+                    break;
+                } else if (i == kThrottlingSeverityCount - 1) {
+                    LOG(ERROR) << sensor_name << ":all thresholds are NAN";
+                    trip_update = false;
+                    break;
+                }
+            }
+            monitored_sensors->insert(sensor_info.first);
+        }
+
+        if (!trip_update) {
+            LOG(INFO) << "config Sensor: " << sensor_info.first
+                      << " to default polling interval: " << kMinPollIntervalMs.count();
+            setMinTimeout(&sensor_info.second);
+        }
+    }
+}
+
+bool ThermalHelper::fillCurrentTemperatures(bool filterType, bool filterCallback,
+                                            TemperatureType type,
+                                            std::vector<Temperature> *temperatures) {
+    std::vector<Temperature> ret;
+    for (const auto &name_info_pair : sensor_info_map_) {
+        Temperature temp;
+        if (name_info_pair.second.is_hidden) {
+            continue;
+        }
+        if (filterType && name_info_pair.second.type != type) {
+            continue;
+        }
+        if (filterCallback && !name_info_pair.second.send_cb) {
+            continue;
+        }
+        if (readTemperature(name_info_pair.first, &temp, nullptr, false)) {
+            ret.emplace_back(std::move(temp));
+        } else {
+            LOG(ERROR) << __func__
+                       << ": error reading temperature for sensor: " << name_info_pair.first;
+        }
+    }
+    *temperatures = ret;
+    return ret.size() > 0;
+}
+
+bool ThermalHelper::fillTemperatureThresholds(bool filterType, TemperatureType type,
+                                              std::vector<TemperatureThreshold> *thresholds) const {
+    std::vector<TemperatureThreshold> ret;
+    for (const auto &name_info_pair : sensor_info_map_) {
+        TemperatureThreshold temp;
+        if (name_info_pair.second.is_hidden) {
+            continue;
+        }
+        if (filterType && name_info_pair.second.type != type) {
+            continue;
+        }
+        if (readTemperatureThreshold(name_info_pair.first, &temp)) {
+            ret.emplace_back(std::move(temp));
+        } else {
+            LOG(ERROR) << __func__ << ": error reading temperature threshold for sensor: "
+                       << name_info_pair.first;
+            return false;
+        }
+    }
+    *thresholds = ret;
+    return ret.size() > 0;
+}
+
+bool ThermalHelper::fillCurrentCoolingDevices(bool filterType, CoolingType type,
+                                              std::vector<CoolingDevice> *cooling_devices) const {
+    std::vector<CoolingDevice> ret;
+    for (const auto &name_info_pair : cooling_device_info_map_) {
+        CoolingDevice value;
+        if (filterType && name_info_pair.second.type != type) {
+            continue;
+        }
+        if (readCoolingDevice(name_info_pair.first, &value)) {
+            ret.emplace_back(std::move(value));
+        } else {
+            LOG(ERROR) << __func__ << ": error reading cooling device: " << name_info_pair.first;
+            return false;
+        }
+    }
+    *cooling_devices = ret;
+    return ret.size() > 0;
+}
+
+bool ThermalHelper::readDataByType(std::string_view sensor_data, float *reading_value,
+                                   const SensorFusionType type, const bool force_no_cache,
+                                   std::map<std::string, float> *sensor_log_map) {
+    switch (type) {
+        case SensorFusionType::SENSOR:
+            if (!readThermalSensor(sensor_data.data(), reading_value, force_no_cache,
+                                   sensor_log_map)) {
+                LOG(ERROR) << "Failed to get " << sensor_data.data() << " data";
+                return false;
+            }
+            break;
+        case SensorFusionType::ODPM:
+            *reading_value = GetPowerStatusMap().at(sensor_data.data()).last_updated_avg_power;
+            if (std::isnan(*reading_value)) {
+                LOG(INFO) << "Power data " << sensor_data.data() << " is under collecting";
+                return false;
+            }
+            (*sensor_log_map)[sensor_data.data()] = *reading_value;
+            break;
+        default:
+            break;
+    }
+    return true;
+}
+
+bool ThermalHelper::readThermalSensor(std::string_view sensor_name, float *temp,
+                                      const bool force_no_cache,
+                                      std::map<std::string, float> *sensor_log_map) {
+    float temp_val = 0.0;
+    std::string file_reading;
+    boot_clock::time_point now = boot_clock::now();
+
+    ATRACE_NAME(StringPrintf("ThermalHelper::readThermalSensor - %s", sensor_name.data()).c_str());
+    if (!(sensor_info_map_.count(sensor_name.data()) &&
+          sensor_status_map_.count(sensor_name.data()))) {
+        return false;
+    }
+
+    const auto &sensor_info = sensor_info_map_.at(sensor_name.data());
+    auto &sensor_status = sensor_status_map_.at(sensor_name.data());
+
+    {
+        std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+        if (sensor_status.emul_setting != nullptr &&
+            !isnan(sensor_status.emul_setting->emul_temp)) {
+            *temp = sensor_status.emul_setting->emul_temp;
+            return true;
+        }
+    }
+
+    // Check if thermal data need to be read from cache
+    if (!force_no_cache &&
+        (sensor_status.thermal_cached.timestamp != boot_clock::time_point::min()) &&
+        (std::chrono::duration_cast<std::chrono::milliseconds>(
+                 now - sensor_status.thermal_cached.timestamp) < sensor_info.time_resolution) &&
+        !isnan(sensor_status.thermal_cached.temp)) {
+        *temp = sensor_status.thermal_cached.temp;
+        (*sensor_log_map)[sensor_name.data()] = *temp;
+        ATRACE_INT((sensor_name.data() + std::string("-cached")).c_str(), static_cast<int>(*temp));
+        return true;
+    }
+
+    // Reading thermal sensor according to it's composition
+    if (sensor_info.virtual_sensor_info == nullptr) {
+        if (!thermal_sensors_.readThermalFile(sensor_name.data(), &file_reading)) {
+            return false;
+        }
+
+        if (file_reading.empty()) {
+            LOG(ERROR) << "failed to read sensor: " << sensor_name;
+            return false;
+        }
+        *temp = std::stof(::android::base::Trim(file_reading));
+    } else {
+        for (size_t i = 0; i < sensor_info.virtual_sensor_info->linked_sensors.size(); i++) {
+            float sensor_reading = 0.0;
+            // Get the sensor reading data
+            if (!readDataByType(sensor_info.virtual_sensor_info->linked_sensors[i], &sensor_reading,
+                                sensor_info.virtual_sensor_info->linked_sensors_type[i],
+                                force_no_cache, sensor_log_map)) {
+                LOG(ERROR) << "Failed to read " << sensor_name.data() << "'s linked sensor "
+                           << sensor_info.virtual_sensor_info->linked_sensors[i];
+            }
+            if (std::isnan(sensor_info.virtual_sensor_info->coefficients[i])) {
+                return false;
+            }
+            float coefficient = sensor_info.virtual_sensor_info->coefficients[i];
+            switch (sensor_info.virtual_sensor_info->formula) {
+                case FormulaOption::COUNT_THRESHOLD:
+                    if ((coefficient < 0 && sensor_reading < -coefficient) ||
+                        (coefficient >= 0 && sensor_reading >= coefficient))
+                        temp_val += 1;
+                    break;
+                case FormulaOption::WEIGHTED_AVG:
+                    temp_val += sensor_reading * coefficient;
+                    break;
+                case FormulaOption::MAXIMUM:
+                    if (i == 0)
+                        temp_val = std::numeric_limits<float>::lowest();
+                    if (sensor_reading * coefficient > temp_val)
+                        temp_val = sensor_reading * coefficient;
+                    break;
+                case FormulaOption::MINIMUM:
+                    if (i == 0)
+                        temp_val = std::numeric_limits<float>::max();
+                    if (sensor_reading * coefficient < temp_val)
+                        temp_val = sensor_reading * coefficient;
+                    break;
+                default:
+                    break;
+            }
+        }
+        *temp = (temp_val + sensor_info.virtual_sensor_info->offset);
+    }
+    (*sensor_log_map)[sensor_name.data()] = *temp;
+    ATRACE_INT(sensor_name.data(), static_cast<int>(*temp));
+
+    {
+        std::unique_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+        sensor_status.thermal_cached.temp = *temp;
+        sensor_status.thermal_cached.timestamp = now;
+    }
+    auto real_temp = (*temp) * sensor_info.multiplier;
+    thermal_stats_helper_.updateSensorTempStatsByThreshold(sensor_name, real_temp);
+    return true;
+}
+
+// This is called in the different thread context and will update sensor_status
+// uevent_sensors is the set of sensors which trigger uevent from thermal core driver.
+std::chrono::milliseconds ThermalHelper::thermalWatcherCallbackFunc(
+        const std::set<std::string> &uevent_sensors) {
+    std::vector<Temperature> temps;
+    std::vector<std::string> cooling_devices_to_update;
+    boot_clock::time_point now = boot_clock::now();
+    auto min_sleep_ms = std::chrono::milliseconds::max();
+    bool power_data_is_updated = false;
+
+    ATRACE_CALL();
+    for (auto &name_status_pair : sensor_status_map_) {
+        bool force_update = false;
+        bool force_no_cache = false;
+        Temperature temp;
+        TemperatureThreshold threshold;
+        SensorStatus &sensor_status = name_status_pair.second;
+        const SensorInfo &sensor_info = sensor_info_map_.at(name_status_pair.first);
+
+        // Only handle the sensors in allow list
+        if (!sensor_info.is_watch) {
+            continue;
+        }
+
+        ATRACE_NAME(StringPrintf("ThermalHelper::thermalWatcherCallbackFunc - %s",
+                                 name_status_pair.first.data())
+                            .c_str());
+
+        std::chrono::milliseconds time_elapsed_ms = std::chrono::milliseconds::zero();
+        auto sleep_ms = (sensor_status.severity != ThrottlingSeverity::NONE)
+                                ? sensor_info.passive_delay
+                                : sensor_info.polling_delay;
+
+        if (sensor_info.virtual_sensor_info != nullptr &&
+            !sensor_info.virtual_sensor_info->trigger_sensors.empty()) {
+            for (size_t i = 0; i < sensor_info.virtual_sensor_info->trigger_sensors.size(); i++) {
+                const auto &trigger_sensor_status =
+                        sensor_status_map_.at(sensor_info.virtual_sensor_info->trigger_sensors[i]);
+                if (trigger_sensor_status.severity != ThrottlingSeverity::NONE) {
+                    sleep_ms = sensor_info.passive_delay;
+                    break;
+                }
+            }
+        }
+        // Check if the sensor need to be updated
+        if (sensor_status.last_update_time == boot_clock::time_point::min()) {
+            force_update = true;
+        } else {
+            time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+                    now - sensor_status.last_update_time);
+            if (uevent_sensors.size()) {
+                if (sensor_info.virtual_sensor_info != nullptr) {
+                    for (size_t i = 0; i < sensor_info.virtual_sensor_info->trigger_sensors.size();
+                         i++) {
+                        if (uevent_sensors.find(
+                                    sensor_info.virtual_sensor_info->trigger_sensors[i]) !=
+                            uevent_sensors.end()) {
+                            force_update = true;
+                            break;
+                        }
+                    }
+                } else if (uevent_sensors.find(name_status_pair.first) != uevent_sensors.end()) {
+                    force_update = true;
+                    force_no_cache = true;
+                }
+            } else if (time_elapsed_ms > sleep_ms) {
+                force_update = true;
+            }
+        }
+        {
+            std::lock_guard<std::shared_mutex> _lock(sensor_status_map_mutex_);
+            if (sensor_status.emul_setting != nullptr &&
+                sensor_status.emul_setting->pending_update) {
+                force_update = true;
+                sensor_status.emul_setting->pending_update = false;
+                LOG(INFO) << "Update " << name_status_pair.first.data()
+                          << " right away with emul setting";
+            }
+        }
+        LOG(VERBOSE) << "sensor " << name_status_pair.first
+                     << ": time_elapsed=" << time_elapsed_ms.count()
+                     << ", sleep_ms=" << sleep_ms.count() << ", force_update = " << force_update
+                     << ", force_no_cache = " << force_no_cache;
+
+        if (!force_update) {
+            auto timeout_remaining = sleep_ms - time_elapsed_ms;
+            if (min_sleep_ms > timeout_remaining) {
+                min_sleep_ms = timeout_remaining;
+            }
+            LOG(VERBOSE) << "sensor " << name_status_pair.first
+                         << ": timeout_remaining=" << timeout_remaining.count();
+            continue;
+        }
+
+        std::pair<ThrottlingSeverity, ThrottlingSeverity> throttling_status;
+        if (!readTemperature(name_status_pair.first, &temp, &throttling_status, force_no_cache)) {
+            LOG(ERROR) << __func__
+                       << ": error reading temperature for sensor: " << name_status_pair.first;
+            continue;
+        }
+        if (!readTemperatureThreshold(name_status_pair.first, &threshold)) {
+            LOG(ERROR) << __func__ << ": error reading temperature threshold for sensor: "
+                       << name_status_pair.first;
+            continue;
+        }
+
+        {
+            // writer lock
+            std::unique_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+            if (throttling_status.first != sensor_status.prev_hot_severity) {
+                sensor_status.prev_hot_severity = throttling_status.first;
+            }
+            if (throttling_status.second != sensor_status.prev_cold_severity) {
+                sensor_status.prev_cold_severity = throttling_status.second;
+            }
+            if (temp.throttlingStatus != sensor_status.severity) {
+                temps.push_back(temp);
+                sensor_status.severity = temp.throttlingStatus;
+                sleep_ms = (sensor_status.severity != ThrottlingSeverity::NONE)
+                                   ? sensor_info.passive_delay
+                                   : sensor_info.polling_delay;
+            }
+        }
+
+        if (!power_data_is_updated) {
+            power_files_.refreshPowerStatus();
+            power_data_is_updated = true;
+        }
+
+        if (sensor_status.severity == ThrottlingSeverity::NONE) {
+            thermal_throttling_.clearThrottlingData(name_status_pair.first, sensor_info);
+        } else {
+            // update thermal throttling request
+            thermal_throttling_.thermalThrottlingUpdate(
+                    temp, sensor_info, sensor_status.severity, time_elapsed_ms,
+                    power_files_.GetPowerStatusMap(), cooling_device_info_map_);
+        }
+
+        thermal_throttling_.computeCoolingDevicesRequest(
+                name_status_pair.first, sensor_info, sensor_status.severity,
+                &cooling_devices_to_update, &thermal_stats_helper_);
+        if (min_sleep_ms > sleep_ms) {
+            min_sleep_ms = sleep_ms;
+        }
+
+        LOG(VERBOSE) << "Sensor " << name_status_pair.first << ": sleep_ms=" << sleep_ms.count()
+                     << ", min_sleep_ms voting result=" << min_sleep_ms.count();
+        sensor_status.last_update_time = now;
+    }
+
+    if (!cooling_devices_to_update.empty()) {
+        updateCoolingDevices(cooling_devices_to_update);
+    }
+
+    if (!temps.empty()) {
+        for (const auto &t : temps) {
+            if (sensor_info_map_.at(t.name).send_cb && cb_) {
+                cb_(t);
+            }
+
+            if (sensor_info_map_.at(t.name).send_powerhint && isAidlPowerHalExist()) {
+                sendPowerExtHint(t);
+            }
+        }
+    }
+
+    int count_failed_reporting = thermal_stats_helper_.reportStats();
+    if (count_failed_reporting != 0) {
+        LOG(ERROR) << "Failed to report " << count_failed_reporting << " thermal stats";
+    }
+
+    return min_sleep_ms;
+}
+
+bool ThermalHelper::connectToPowerHal() {
+    return power_hal_service_.connect();
+}
+
+void ThermalHelper::updateSupportedPowerHints() {
+    for (auto const &name_status_pair : sensor_info_map_) {
+        if (!(name_status_pair.second.send_powerhint)) {
+            continue;
+        }
+        ThrottlingSeverity current_severity = ThrottlingSeverity::NONE;
+        for (const auto &severity : ::ndk::enum_range<ThrottlingSeverity>()) {
+            if (severity == ThrottlingSeverity::NONE) {
+                supported_powerhint_map_[name_status_pair.first][ThrottlingSeverity::NONE] =
+                        ThrottlingSeverity::NONE;
+                continue;
+            }
+
+            bool isSupported = false;
+            ndk::ScopedAStatus isSupportedResult;
+
+            if (power_hal_service_.isPowerHalExtConnected()) {
+                isSupported = power_hal_service_.isModeSupported(name_status_pair.first, severity);
+            }
+            if (isSupported)
+                current_severity = severity;
+            supported_powerhint_map_[name_status_pair.first][severity] = current_severity;
+        }
+    }
+}
+
+void ThermalHelper::sendPowerExtHint(const Temperature &t) {
+    ATRACE_CALL();
+    std::lock_guard<std::shared_mutex> lock(sensor_status_map_mutex_);
+    ThrottlingSeverity prev_hint_severity;
+    prev_hint_severity = sensor_status_map_.at(t.name).prev_hint_severity;
+    ThrottlingSeverity current_hint_severity = supported_powerhint_map_[t.name][t.throttlingStatus];
+
+    if (prev_hint_severity == current_hint_severity)
+        return;
+
+    if (prev_hint_severity != ThrottlingSeverity::NONE) {
+        power_hal_service_.setMode(t.name, prev_hint_severity, false);
+    }
+
+    if (current_hint_severity != ThrottlingSeverity::NONE) {
+        power_hal_service_.setMode(t.name, current_hint_severity, true);
+    }
+
+    sensor_status_map_[t.name].prev_hint_severity = current_hint_severity;
+}
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/thermal-helper.h b/aidl/thermal/thermal-helper.h
new file mode 100644 (file)
index 0000000..3b790e3
--- /dev/null
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/thermal/IThermal.h>
+
+#include <array>
+#include <chrono>
+#include <map>
+#include <mutex>
+#include <shared_mutex>
+#include <string>
+#include <string_view>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+#include "utils/power_files.h"
+#include "utils/powerhal_helper.h"
+#include "utils/thermal_files.h"
+#include "utils/thermal_info.h"
+#include "utils/thermal_stats_helper.h"
+#include "utils/thermal_throttling.h"
+#include "utils/thermal_watcher.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using ::android::sp;
+
+using NotificationCallback = std::function<void(const Temperature &t)>;
+
+// Get thermal_zone type
+bool getThermalZoneTypeById(int tz_id, std::string *);
+
+struct ThermalSample {
+    float temp;
+    boot_clock::time_point timestamp;
+};
+
+struct EmulSetting {
+    float emul_temp;
+    int emul_severity;
+    bool pending_update;
+};
+
+struct SensorStatus {
+    ThrottlingSeverity severity;
+    ThrottlingSeverity prev_hot_severity;
+    ThrottlingSeverity prev_cold_severity;
+    ThrottlingSeverity prev_hint_severity;
+    boot_clock::time_point last_update_time;
+    ThermalSample thermal_cached;
+    std::unique_ptr<EmulSetting> emul_setting;
+};
+
+class ThermalHelper {
+  public:
+    explicit ThermalHelper(const NotificationCallback &cb);
+    ~ThermalHelper() = default;
+
+    bool fillCurrentTemperatures(bool filterType, bool filterCallback, TemperatureType type,
+                                 std::vector<Temperature> *temperatures);
+    bool fillTemperatureThresholds(bool filterType, TemperatureType type,
+                                   std::vector<TemperatureThreshold> *thresholds) const;
+    bool fillCurrentCoolingDevices(bool filterType, CoolingType type,
+                                   std::vector<CoolingDevice> *coolingdevices) const;
+    bool emulTemp(std::string_view target_sensor, const float temp);
+    bool emulSeverity(std::string_view target_sensor, const int severity);
+    bool emulClear(std::string_view target_sensor);
+
+    // Disallow copy and assign.
+    ThermalHelper(const ThermalHelper &) = delete;
+    void operator=(const ThermalHelper &) = delete;
+
+    bool isInitializedOk() const { return is_initialized_; }
+
+    // Read the temperature of a single sensor.
+    bool readTemperature(std::string_view sensor_name, Temperature *out);
+    bool readTemperature(
+            std::string_view sensor_name, Temperature *out,
+            std::pair<ThrottlingSeverity, ThrottlingSeverity> *throtting_status = nullptr,
+            const bool force_sysfs = false);
+
+    bool readTemperatureThreshold(std::string_view sensor_name, TemperatureThreshold *out) const;
+    // Read the value of a single cooling device.
+    bool readCoolingDevice(std::string_view cooling_device, CoolingDevice *out) const;
+    // Get SensorInfo Map
+    const std::unordered_map<std::string, SensorInfo> &GetSensorInfoMap() const {
+        return sensor_info_map_;
+    }
+    // Get CdevInfo Map
+    const std::unordered_map<std::string, CdevInfo> &GetCdevInfoMap() const {
+        return cooling_device_info_map_;
+    }
+    // Get SensorStatus Map
+    const std::unordered_map<std::string, SensorStatus> &GetSensorStatusMap() const {
+        std::shared_lock<std::shared_mutex> _lock(sensor_status_map_mutex_);
+        return sensor_status_map_;
+    }
+    // Get ThermalThrottling Map
+    const std::unordered_map<std::string, ThermalThrottlingStatus> &GetThermalThrottlingStatusMap()
+            const {
+        return thermal_throttling_.GetThermalThrottlingStatusMap();
+    }
+    // Get PowerRailInfo Map
+    const std::unordered_map<std::string, PowerRailInfo> &GetPowerRailInfoMap() const {
+        return power_files_.GetPowerRailInfoMap();
+    }
+
+    // Get PowerStatus Map
+    const std::unordered_map<std::string, PowerStatus> &GetPowerStatusMap() const {
+        return power_files_.GetPowerStatusMap();
+    }
+
+    // Get Thermal Stats Sensor Map
+    const std::unordered_map<std::string, SensorTempStats> GetSensorTempStatsSnapshot() {
+        return thermal_stats_helper_.GetSensorTempStatsSnapshot();
+    }
+    // Get Thermal Stats Sensor, Binded Cdev State Request Map
+    const std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
+    GetSensorCoolingDeviceRequestStatsSnapshot() {
+        return thermal_stats_helper_.GetSensorCoolingDeviceRequestStatsSnapshot();
+    }
+
+    void sendPowerExtHint(const Temperature &t);
+    bool isAidlPowerHalExist() { return power_hal_service_.isAidlPowerHalExist(); }
+    bool isPowerHalConnected() { return power_hal_service_.isPowerHalConnected(); }
+    bool isPowerHalExtConnected() { return power_hal_service_.isPowerHalExtConnected(); }
+
+  private:
+    bool initializeSensorMap(const std::unordered_map<std::string, std::string> &path_map);
+    bool initializeCoolingDevices(const std::unordered_map<std::string, std::string> &path_map);
+    bool isSubSensorValid(std::string_view sensor_data, const SensorFusionType sensor_fusion_type);
+    void setMinTimeout(SensorInfo *sensor_info);
+    void initializeTrip(const std::unordered_map<std::string, std::string> &path_map,
+                        std::set<std::string> *monitored_sensors, bool thermal_genl_enabled);
+    void clearAllThrottling();
+    // For thermal_watcher_'s polling thread, return the sleep interval
+    std::chrono::milliseconds thermalWatcherCallbackFunc(
+            const std::set<std::string> &uevent_sensors);
+    // Return hot and cold severity status as std::pair
+    std::pair<ThrottlingSeverity, ThrottlingSeverity> getSeverityFromThresholds(
+            const ThrottlingArray &hot_thresholds, const ThrottlingArray &cold_thresholds,
+            const ThrottlingArray &hot_hysteresis, const ThrottlingArray &cold_hysteresis,
+            ThrottlingSeverity prev_hot_severity, ThrottlingSeverity prev_cold_severity,
+            float value) const;
+    // Read sensor data according to the type
+    bool readDataByType(std::string_view sensor_data, float *reading_value,
+                        const SensorFusionType type, const bool force_no_cache,
+                        std::map<std::string, float> *sensor_log_map);
+    // Read temperature data according to thermal sensor's info
+    bool readThermalSensor(std::string_view sensor_name, float *temp, const bool force_sysfs,
+                           std::map<std::string, float> *sensor_log_map);
+    bool connectToPowerHal();
+    void updateSupportedPowerHints();
+    void updateCoolingDevices(const std::vector<std::string> &cooling_devices_to_update);
+    sp<ThermalWatcher> thermal_watcher_;
+    PowerFiles power_files_;
+    ThermalFiles thermal_sensors_;
+    ThermalFiles cooling_devices_;
+    ThermalThrottling thermal_throttling_;
+    bool is_initialized_;
+    const NotificationCallback cb_;
+    std::unordered_map<std::string, CdevInfo> cooling_device_info_map_;
+    std::unordered_map<std::string, SensorInfo> sensor_info_map_;
+    std::unordered_map<std::string, std::unordered_map<ThrottlingSeverity, ThrottlingSeverity>>
+            supported_powerhint_map_;
+    PowerHalService power_hal_service_;
+    ThermalStatsHelper thermal_stats_helper_;
+    mutable std::shared_mutex sensor_status_map_mutex_;
+    std::unordered_map<std::string, SensorStatus> sensor_status_map_;
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/config_schema.json b/aidl/thermal/utils/config_schema.json
new file mode 100644 (file)
index 0000000..fd493e1
--- /dev/null
@@ -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 (file)
index 0000000..50cc5b6
--- /dev/null
@@ -0,0 +1,342 @@
+
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
+
+#include "power_files.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <dirent.h>
+#include <utils/Trace.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr std::string_view kDeviceType("iio:device");
+constexpr std::string_view kIioRootDir("/sys/bus/iio/devices");
+constexpr std::string_view kEnergyValueNode("energy_value");
+
+using ::android::base::ReadFileToString;
+using ::android::base::StringPrintf;
+
+bool PowerFiles::registerPowerRailsToWatch(const Json::Value &config) {
+    if (!ParsePowerRailInfo(config, &power_rail_info_map_)) {
+        LOG(ERROR) << "Failed to parse power rail info config";
+        return false;
+    }
+
+    if (!power_rail_info_map_.size()) {
+        LOG(INFO) << " No power rail info config found";
+        return true;
+    }
+
+    if (!findEnergySourceToWatch()) {
+        LOG(ERROR) << "Cannot find energy source";
+        return false;
+    }
+
+    if (!energy_info_map_.size() && !updateEnergyValues()) {
+        LOG(ERROR) << "Faield to update energy info";
+        return false;
+    }
+
+    for (const auto &power_rail_info_pair : power_rail_info_map_) {
+        std::vector<std::queue<PowerSample>> power_history;
+        if (!power_rail_info_pair.second.power_sample_count ||
+            power_rail_info_pair.second.power_sample_delay == std::chrono::milliseconds::max()) {
+            continue;
+        }
+
+        PowerSample power_sample = {
+                .energy_counter = 0,
+                .duration = 0,
+        };
+
+        if (power_rail_info_pair.second.virtual_power_rail_info != nullptr &&
+            power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size()) {
+            for (size_t i = 0;
+                 i < power_rail_info_pair.second.virtual_power_rail_info->linked_power_rails.size();
+                 ++i) {
+                if (!energy_info_map_.count(power_rail_info_pair.second.virtual_power_rail_info
+                                                    ->linked_power_rails[i])) {
+                    LOG(ERROR) << " Could not find energy source "
+                               << power_rail_info_pair.second.virtual_power_rail_info
+                                          ->linked_power_rails[i];
+                    return false;
+                }
+                power_history.emplace_back(std::queue<PowerSample>());
+                for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
+                    power_history[i].emplace(power_sample);
+                }
+            }
+        } else {
+            if (energy_info_map_.count(power_rail_info_pair.first)) {
+                power_history.emplace_back(std::queue<PowerSample>());
+                for (int j = 0; j < power_rail_info_pair.second.power_sample_count; j++) {
+                    power_history[0].emplace(power_sample);
+                }
+            } else {
+                LOG(ERROR) << "Could not find energy source " << power_rail_info_pair.first;
+                return false;
+            }
+        }
+
+        if (power_history.size()) {
+            power_status_map_[power_rail_info_pair.first] = {
+                    .power_history = power_history,
+                    .last_update_time = boot_clock::time_point::min(),
+                    .last_updated_avg_power = NAN,
+            };
+        } else {
+            LOG(ERROR) << "power history size is zero";
+            return false;
+        }
+        LOG(INFO) << "Successfully to register power rail " << power_rail_info_pair.first;
+    }
+    return true;
+}
+
+bool PowerFiles::findEnergySourceToWatch(void) {
+    std::string devicePath;
+
+    if (energy_path_set_.size()) {
+        return true;
+    }
+
+    std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kIioRootDir.data()), closedir);
+    if (!dir) {
+        PLOG(ERROR) << "Error opening directory" << kIioRootDir;
+        return false;
+    }
+
+    // Find any iio:devices that support energy_value
+    while (struct dirent *ent = readdir(dir.get())) {
+        std::string devTypeDir = ent->d_name;
+        if (devTypeDir.find(kDeviceType) != std::string::npos) {
+            devicePath = StringPrintf("%s/%s", kIioRootDir.data(), devTypeDir.data());
+            std::string deviceEnergyContent;
+
+            if (!ReadFileToString(StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()),
+                                  &deviceEnergyContent)) {
+            } else if (deviceEnergyContent.size()) {
+                energy_path_set_.emplace(
+                        StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()));
+            }
+        }
+    }
+
+    if (!energy_path_set_.size()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool PowerFiles::updateEnergyValues(void) {
+    std::string deviceEnergyContent;
+    std::string deviceEnergyContents;
+    std::string line;
+
+    ATRACE_CALL();
+    for (const auto &path : energy_path_set_) {
+        if (!::android::base::ReadFileToString(path, &deviceEnergyContent)) {
+            LOG(ERROR) << "Failed to read energy content from " << path;
+            return false;
+        } else {
+            deviceEnergyContents.append(deviceEnergyContent);
+        }
+    }
+
+    std::istringstream energyData(deviceEnergyContents);
+
+    while (std::getline(energyData, line)) {
+        /* Read rail energy */
+        uint64_t energy_counter = 0;
+        uint64_t duration = 0;
+
+        /* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */
+        auto start_pos = line.find("T=");
+        auto end_pos = line.find(')');
+        if (start_pos != std::string::npos) {
+            duration =
+                    strtoul(line.substr(start_pos + 2, end_pos - start_pos - 2).c_str(), NULL, 10);
+        } else {
+            continue;
+        }
+
+        start_pos = line.find(")[");
+        end_pos = line.find(']');
+        std::string railName;
+        if (start_pos != std::string::npos) {
+            railName = line.substr(start_pos + 2, end_pos - start_pos - 2);
+        } else {
+            continue;
+        }
+
+        start_pos = line.find("],");
+        if (start_pos != std::string::npos) {
+            energy_counter = strtoul(line.substr(start_pos + 2).c_str(), NULL, 10);
+        } else {
+            continue;
+        }
+
+        energy_info_map_[railName] = {
+                .energy_counter = energy_counter,
+                .duration = duration,
+        };
+    }
+
+    return true;
+}
+
+float PowerFiles::updateAveragePower(std::string_view power_rail,
+                                     std::queue<PowerSample> *power_history) {
+    float avg_power = NAN;
+
+    if (!energy_info_map_.count(power_rail.data())) {
+        LOG(ERROR) << " Could not find power rail " << power_rail.data();
+        return avg_power;
+    }
+    const auto last_sample = power_history->front();
+    const auto curr_sample = energy_info_map_.at(power_rail.data());
+    const auto duration = curr_sample.duration - last_sample.duration;
+    const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter;
+
+    if (!last_sample.duration) {
+        LOG(VERBOSE) << "Power rail " << power_rail.data()
+                     << ": all power samples have not been collected yet";
+    } else if (duration <= 0 || deltaEnergy < 0) {
+        LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: duration = " << duration
+                   << ", deltaEnergy = " << deltaEnergy;
+
+        return avg_power;
+    } else {
+        avg_power = static_cast<float>(deltaEnergy) / static_cast<float>(duration);
+        LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << avg_power
+                     << ", duration = " << duration << ", deltaEnergy = " << deltaEnergy;
+    }
+
+    power_history->pop();
+    power_history->push(curr_sample);
+
+    return avg_power;
+}
+
+float PowerFiles::updatePowerRail(std::string_view power_rail) {
+    float avg_power = NAN;
+
+    if (!power_rail_info_map_.count(power_rail.data())) {
+        return avg_power;
+    }
+
+    if (!power_status_map_.count(power_rail.data())) {
+        return avg_power;
+    }
+
+    const auto &power_rail_info = power_rail_info_map_.at(power_rail.data());
+    auto &power_status = power_status_map_.at(power_rail.data());
+
+    boot_clock::time_point now = boot_clock::now();
+    auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+            now - power_status.last_update_time);
+
+    if (power_status.last_update_time != boot_clock::time_point::min() &&
+        time_elapsed_ms < power_rail_info.power_sample_delay) {
+        return power_status.last_updated_avg_power;
+    }
+
+    if (!energy_info_map_.size() && !updateEnergyValues()) {
+        LOG(ERROR) << "Failed to update energy values";
+        return avg_power;
+    }
+
+    if (power_rail_info.virtual_power_rail_info == nullptr) {
+        avg_power = updateAveragePower(power_rail, &power_status.power_history[0]);
+    } else {
+        const auto offset = power_rail_info.virtual_power_rail_info->offset;
+        float avg_power_val = 0.0;
+        for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
+             i++) {
+            float coefficient = power_rail_info.virtual_power_rail_info->coefficients[i];
+            float avg_power_number = updateAveragePower(
+                    power_rail_info.virtual_power_rail_info->linked_power_rails[i],
+                    &power_status.power_history[i]);
+
+            switch (power_rail_info.virtual_power_rail_info->formula) {
+                case FormulaOption::COUNT_THRESHOLD:
+                    if ((coefficient < 0 && avg_power_number < -coefficient) ||
+                        (coefficient >= 0 && avg_power_number >= coefficient))
+                        avg_power_val += 1;
+                    break;
+                case FormulaOption::WEIGHTED_AVG:
+                    avg_power_val += avg_power_number * coefficient;
+                    break;
+                case FormulaOption::MAXIMUM:
+                    if (i == 0)
+                        avg_power_val = std::numeric_limits<float>::lowest();
+                    if (avg_power_number * coefficient > avg_power_val)
+                        avg_power_val = avg_power_number * coefficient;
+                    break;
+                case FormulaOption::MINIMUM:
+                    if (i == 0)
+                        avg_power_val = std::numeric_limits<float>::max();
+                    if (avg_power_number * coefficient < avg_power_val)
+                        avg_power_val = avg_power_number * coefficient;
+                    break;
+                default:
+                    break;
+            }
+        }
+        if (avg_power_val >= 0) {
+            avg_power_val = avg_power_val + offset;
+        }
+
+        avg_power = avg_power_val;
+    }
+
+    if (avg_power < 0) {
+        avg_power = NAN;
+    }
+
+    power_status.last_updated_avg_power = avg_power;
+    power_status.last_update_time = now;
+    return avg_power;
+}
+
+bool PowerFiles::refreshPowerStatus(void) {
+    if (!updateEnergyValues()) {
+        LOG(ERROR) << "Failed to update energy values";
+        return false;
+    }
+
+    for (const auto &power_status_pair : power_status_map_) {
+        updatePowerRail(power_status_pair.first);
+    }
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/power_files.h b/aidl/thermal/utils/power_files.h
new file mode 100644 (file)
index 0000000..bcf8e82
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/chrono_utils.h>
+
+#include <chrono>
+#include <queue>
+#include <shared_mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "thermal_info.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using ::android::base::boot_clock;
+
+struct PowerSample {
+    uint64_t energy_counter;
+    uint64_t duration;
+};
+
+struct PowerStatus {
+    boot_clock::time_point last_update_time;
+    // A vector to record the queues of power sample history.
+    std::vector<std::queue<PowerSample>> power_history;
+    float last_updated_avg_power;
+};
+
+// A helper class for monitoring power rails.
+class PowerFiles {
+  public:
+    PowerFiles() = default;
+    ~PowerFiles() = default;
+    // Disallow copy and assign.
+    PowerFiles(const PowerFiles &) = delete;
+    void operator=(const PowerFiles &) = delete;
+    bool registerPowerRailsToWatch(const Json::Value &config);
+    // Update the power data from ODPM sysfs
+    bool refreshPowerStatus(void);
+    // Get power status map
+    const std::unordered_map<std::string, PowerStatus> &GetPowerStatusMap() const {
+        std::shared_lock<std::shared_mutex> _lock(power_status_map_mutex_);
+        return power_status_map_;
+    }
+    // Get power rail info map
+    const std::unordered_map<std::string, PowerRailInfo> &GetPowerRailInfoMap() const {
+        return power_rail_info_map_;
+    }
+
+  private:
+    // Update energy value to energy_info_map_, return false if the value is failed to update.
+    bool updateEnergyValues(void);
+    // Compute the average power for physical power rail.
+    float updateAveragePower(std::string_view power_rail, std::queue<PowerSample> *power_history);
+    // Update the power data for the target power rail.
+    float updatePowerRail(std::string_view power_rail);
+    // Find the energy source path, return false if no energy source found.
+    bool findEnergySourceToWatch(void);
+    // The map to record the energy counter for each power rail.
+    std::unordered_map<std::string, PowerSample> energy_info_map_;
+    // The map to record the power data for each thermal sensor.
+    std::unordered_map<std::string, PowerStatus> power_status_map_;
+    mutable std::shared_mutex power_status_map_mutex_;
+    // The map to record the power rail information from thermal config
+    std::unordered_map<std::string, PowerRailInfo> power_rail_info_map_;
+    // The set to store the energy source paths
+    std::unordered_set<std::string> energy_path_set_;
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/powerhal_helper.cpp b/aidl/thermal/utils/powerhal_helper.cpp
new file mode 100644 (file)
index 0000000..c40d262
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "powerhal_helper.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <android/binder_manager.h>
+
+#include <iterator>
+#include <set>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+#include "thermal_info.h"
+#include "thermal_throttling.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using ::android::base::StringPrintf;
+
+PowerHalService::PowerHalService()
+    : power_hal_aidl_exist_(true), power_hal_aidl_(nullptr), power_hal_ext_aidl_(nullptr) {
+    connect();
+}
+
+bool PowerHalService::connect() {
+    std::lock_guard<std::mutex> lock(lock_);
+
+    if (!power_hal_aidl_exist_) {
+        return false;
+    }
+
+    if (power_hal_aidl_ && power_hal_ext_aidl_) {
+        return true;
+    }
+
+    const std::string kInstance = std::string(IPower::descriptor) + "/default";
+    ndk::SpAIBinder power_binder = ndk::SpAIBinder(AServiceManager_getService(kInstance.c_str()));
+    ndk::SpAIBinder ext_power_binder;
+
+    if (power_binder.get() == nullptr) {
+        LOG(ERROR) << "Cannot get Power Hal Binder";
+        power_hal_aidl_exist_ = false;
+        return false;
+    }
+
+    power_hal_aidl_ = IPower::fromBinder(power_binder);
+
+    if (power_hal_aidl_ == nullptr) {
+        power_hal_aidl_exist_ = false;
+        LOG(ERROR) << "Cannot get Power Hal AIDL" << kInstance.c_str();
+        return false;
+    }
+
+    if (STATUS_OK != AIBinder_getExtension(power_binder.get(), ext_power_binder.getR()) ||
+        ext_power_binder.get() == nullptr) {
+        LOG(ERROR) << "Cannot get Power Hal Extension Binder";
+        power_hal_aidl_exist_ = false;
+        return false;
+    }
+
+    power_hal_ext_aidl_ = IPowerExt::fromBinder(ext_power_binder);
+    if (power_hal_ext_aidl_ == nullptr) {
+        LOG(ERROR) << "Cannot get Power Hal Extension AIDL";
+        power_hal_aidl_exist_ = false;
+    }
+
+    return true;
+}
+
+bool PowerHalService::isModeSupported(const std::string &type, const ThrottlingSeverity &t) {
+    bool isSupported = false;
+    if (!connect()) {
+        return false;
+    }
+    std::string power_hint = StringPrintf("THERMAL_%s_%s", type.c_str(), toString(t).c_str());
+    lock_.lock();
+    if (!power_hal_ext_aidl_->isModeSupported(power_hint, &isSupported).isOk()) {
+        LOG(ERROR) << "Fail to check supported mode, Hint: " << power_hint;
+        power_hal_ext_aidl_ = nullptr;
+        power_hal_aidl_ = nullptr;
+        lock_.unlock();
+        return false;
+    }
+    lock_.unlock();
+    return isSupported;
+}
+
+void PowerHalService::setMode(const std::string &type, const ThrottlingSeverity &t,
+                              const bool &enable, const bool error_on_exit) {
+    if (!connect()) {
+        return;
+    }
+
+    std::string power_hint = StringPrintf("THERMAL_%s_%s", type.c_str(), toString(t).c_str());
+    LOG(INFO) << (error_on_exit ? "Resend Hint " : "Send Hint ") << power_hint
+              << " Enable: " << std::boolalpha << enable;
+    lock_.lock();
+    if (!power_hal_ext_aidl_->setMode(power_hint, enable).isOk()) {
+        LOG(ERROR) << "Fail to set mode, Hint: " << power_hint;
+        power_hal_ext_aidl_ = nullptr;
+        power_hal_aidl_ = nullptr;
+        lock_.unlock();
+        if (!error_on_exit) {
+            setMode(type, t, enable, true);
+        }
+        return;
+    }
+    lock_.unlock();
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/powerhal_helper.h b/aidl/thermal/utils/powerhal_helper.h
new file mode 100644 (file)
index 0000000..0769aa2
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/thermal/ThrottlingSeverity.h>
+#include <aidl/google/hardware/power/extension/pixel/IPowerExt.h>
+
+#include <queue>
+#include <shared_mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using ::aidl::android::hardware::power::IPower;
+using ::aidl::google::hardware::power::extension::pixel::IPowerExt;
+
+using CdevRequestStatus = std::unordered_map<std::string, int>;
+
+class PowerHalService {
+  public:
+    PowerHalService();
+    ~PowerHalService() = default;
+    bool connect();
+    bool isAidlPowerHalExist() { return power_hal_aidl_exist_; }
+    bool isModeSupported(const std::string &type, const ThrottlingSeverity &t);
+    bool isPowerHalConnected() { return power_hal_aidl_ != nullptr; }
+    bool isPowerHalExtConnected() { return power_hal_ext_aidl_ != nullptr; }
+    void setMode(const std::string &type, const ThrottlingSeverity &t, const bool &enable,
+                 const bool error_on_exit = false);
+
+  private:
+    bool power_hal_aidl_exist_;
+    std::shared_ptr<IPower> power_hal_aidl_;
+    std::shared_ptr<IPowerExt> power_hal_ext_aidl_;
+    std::mutex lock_;
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_files.cpp b/aidl/thermal/utils/thermal_files.cpp
new file mode 100644 (file)
index 0000000..26aaf45
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
+
+#include "thermal_files.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <utils/Trace.h>
+
+#include <algorithm>
+#include <string_view>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using ::android::base::StringPrintf;
+
+std::string ThermalFiles::getThermalFilePath(std::string_view thermal_name) const {
+    auto sensor_itr = thermal_name_to_path_map_.find(thermal_name.data());
+    if (sensor_itr == thermal_name_to_path_map_.end()) {
+        return "";
+    }
+    return sensor_itr->second;
+}
+
+bool ThermalFiles::addThermalFile(std::string_view thermal_name, std::string_view path) {
+    return thermal_name_to_path_map_.emplace(thermal_name, path).second;
+}
+
+bool ThermalFiles::readThermalFile(std::string_view thermal_name, std::string *data) const {
+    std::string sensor_reading;
+    std::string file_path = getThermalFilePath(std::string_view(thermal_name));
+    *data = "";
+
+    ATRACE_NAME(StringPrintf("ThermalFiles::readThermalFile - %s", thermal_name.data()).c_str());
+    if (file_path.empty()) {
+        PLOG(WARNING) << "Failed to find " << thermal_name << "'s path";
+        return false;
+    }
+
+    if (!::android::base::ReadFileToString(file_path, &sensor_reading)) {
+        PLOG(WARNING) << "Failed to read sensor: " << thermal_name;
+        return false;
+    }
+
+    // Strip the newline.
+    *data = ::android::base::Trim(sensor_reading);
+    return true;
+}
+
+bool ThermalFiles::writeCdevFile(std::string_view cdev_name, std::string_view data) {
+    std::string file_path =
+            getThermalFilePath(::android::base::StringPrintf("%s_%s", cdev_name.data(), "w"));
+
+    ATRACE_NAME(StringPrintf("ThermalFiles::writeCdevFile - %s", cdev_name.data()).c_str());
+    if (!::android::base::WriteStringToFile(data.data(), file_path)) {
+        PLOG(WARNING) << "Failed to write cdev: " << cdev_name << " to " << data.data();
+        return false;
+    }
+
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_files.h b/aidl/thermal/utils/thermal_files.h
new file mode 100644 (file)
index 0000000..4b83780
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+class ThermalFiles {
+  public:
+    ThermalFiles() = default;
+    ~ThermalFiles() = default;
+    ThermalFiles(const ThermalFiles &) = delete;
+    void operator=(const ThermalFiles &) = delete;
+
+    std::string getThermalFilePath(std::string_view thermal_name) const;
+    // Returns true if add was successful, false otherwise.
+    bool addThermalFile(std::string_view thermal_name, std::string_view path);
+    // If thermal_name is not found in the thermal names to path map, this will set
+    // data to empty and return false. If the thermal_name is found and its content
+    // is read, this function will fill in data accordingly then return true.
+    bool readThermalFile(std::string_view thermal_name, std::string *data) const;
+    bool writeCdevFile(std::string_view thermal_name, std::string_view data);
+    size_t getNumThermalFiles() const { return thermal_name_to_path_map_.size(); }
+
+  private:
+    std::unordered_map<std::string, std::string> thermal_name_to_path_map_;
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_info.cpp b/aidl/thermal/utils/thermal_info.cpp
new file mode 100644 (file)
index 0000000..c00bf63
--- /dev/null
@@ -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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <json/reader.h>
+
+#include <cmath>
+#include <unordered_set>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr std::string_view kPowerLinkDisabledProperty("vendor.disable.thermal.powerlink");
+
+namespace {
+
+template <typename T>
+// Return false when failed parsing
+bool getTypeFromString(std::string_view str, T *out) {
+    auto types = ::ndk::enum_range<T>();
+    for (const auto &type : types) {
+        if (::aidl::android::hardware::thermal::toString(type) == str) {
+            *out = type;
+            return true;
+        }
+    }
+    return false;
+}
+
+float getFloatFromValue(const Json::Value &value) {
+    if (value.isString()) {
+        return std::stof(value.asString());
+    } else {
+        return value.asFloat();
+    }
+}
+
+int getIntFromValue(const Json::Value &value) {
+    if (value.isString()) {
+        return (value.asString() == "max") ? std::numeric_limits<int>::max()
+                                           : std::stoul(value.asString());
+    } else {
+        return value.asInt();
+    }
+}
+
+bool getIntFromJsonValues(const Json::Value &values, CdevArray *out, bool inc_check,
+                          bool dec_check) {
+    CdevArray ret;
+
+    if (inc_check && dec_check) {
+        LOG(ERROR) << "Cannot enable inc_check and dec_check at the same time";
+        return false;
+    }
+
+    if (values.size() != kThrottlingSeverityCount) {
+        LOG(ERROR) << "Values size is invalid";
+        return false;
+    } else {
+        int last;
+        for (Json::Value::ArrayIndex i = 0; i < kThrottlingSeverityCount; ++i) {
+            ret[i] = getIntFromValue(values[i]);
+            if (inc_check && ret[i] < last) {
+                LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " min=" << last;
+                return false;
+            }
+            if (dec_check && ret[i] > last) {
+                LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " max=" << last;
+                return false;
+            }
+            last = ret[i];
+            LOG(INFO) << "[" << i << "]: " << ret[i];
+        }
+    }
+
+    *out = ret;
+    return true;
+}
+
+bool getFloatFromJsonValues(const Json::Value &values, ThrottlingArray *out, bool inc_check,
+                            bool dec_check) {
+    ThrottlingArray ret;
+
+    if (inc_check && dec_check) {
+        LOG(ERROR) << "Cannot enable inc_check and dec_check at the same time";
+        return false;
+    }
+
+    if (values.size() != kThrottlingSeverityCount) {
+        LOG(ERROR) << "Values size is invalid";
+        return false;
+    } else {
+        float last = std::nanf("");
+        for (Json::Value::ArrayIndex i = 0; i < kThrottlingSeverityCount; ++i) {
+            ret[i] = getFloatFromValue(values[i]);
+            if (inc_check && !std::isnan(last) && !std::isnan(ret[i]) && ret[i] < last) {
+                LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " min=" << last;
+                return false;
+            }
+            if (dec_check && !std::isnan(last) && !std::isnan(ret[i]) && ret[i] > last) {
+                LOG(FATAL) << "Invalid array[" << i << "]" << ret[i] << " max=" << last;
+                return false;
+            }
+            last = std::isnan(ret[i]) ? last : ret[i];
+            LOG(INFO) << "[" << i << "]: " << ret[i];
+        }
+    }
+
+    *out = ret;
+    return true;
+}
+}  // namespace
+
+bool ParseThermalConfig(std::string_view config_path, Json::Value *config) {
+    std::string json_doc;
+    if (!::android::base::ReadFileToString(config_path.data(), &json_doc)) {
+        LOG(ERROR) << "Failed to read JSON config from " << config_path;
+        return false;
+    }
+    Json::CharReaderBuilder builder;
+    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
+    std::string errorMessage;
+    if (!reader->parse(&*json_doc.begin(), &*json_doc.end(), config, &errorMessage)) {
+        LOG(ERROR) << "Failed to parse JSON config: " << errorMessage;
+        return false;
+    }
+    return true;
+}
+
+bool ParseVirtualSensorInfo(const std::string_view name, const Json::Value &sensor,
+                            std::unique_ptr<VirtualSensorInfo> *virtual_sensor_info) {
+    if (sensor["VirtualSensor"].empty() || !sensor["VirtualSensor"].isBool()) {
+        LOG(INFO) << "Failed to read Sensor[" << name << "]'s VirtualSensor";
+        return true;
+    }
+    bool is_virtual_sensor = sensor["VirtualSensor"].asBool();
+    LOG(INFO) << "Sensor[" << name << "]'s' VirtualSensor: " << is_virtual_sensor;
+    if (!is_virtual_sensor) {
+        return true;
+    }
+    float offset = 0;
+    std::vector<std::string> linked_sensors;
+    std::vector<SensorFusionType> linked_sensors_type;
+    std::vector<std::string> trigger_sensors;
+    std::vector<float> coefficients;
+    FormulaOption formula = FormulaOption::COUNT_THRESHOLD;
+
+    Json::Value values = sensor["Combination"];
+    if (values.size()) {
+        linked_sensors.reserve(values.size());
+        for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+            linked_sensors.emplace_back(values[j].asString());
+            LOG(INFO) << "Sensor[" << name << "]'s Combination[" << j << "]: " << linked_sensors[j];
+        }
+    } else {
+        LOG(ERROR) << "Sensor[" << name << "] has no Combination setting";
+        return false;
+    }
+
+    values = sensor["CombinationType"];
+    if (!values.size()) {
+        linked_sensors_type.reserve(linked_sensors.size());
+        for (size_t j = 0; j < linked_sensors.size(); ++j) {
+            linked_sensors_type.emplace_back(SensorFusionType::SENSOR);
+        }
+    } else if (values.size() != linked_sensors.size()) {
+        LOG(ERROR) << "Sensor[" << name << "] has invalid CombinationType size";
+        return false;
+    } else {
+        for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+            if (values[j].asString().compare("SENSOR") == 0) {
+                linked_sensors_type.emplace_back(SensorFusionType::SENSOR);
+            } else if (values[j].asString().compare("ODPM") == 0) {
+                linked_sensors_type.emplace_back(SensorFusionType::ODPM);
+            } else {
+                LOG(ERROR) << "Sensor[" << name << "] has invalid CombinationType settings "
+                           << values[j].asString();
+                return false;
+            }
+            LOG(INFO) << "Sensor[" << name << "]'s CombinationType[" << j
+                      << "]: " << linked_sensors_type[j];
+        }
+    }
+
+    values = sensor["Coefficient"];
+    if (values.size()) {
+        coefficients.reserve(values.size());
+        for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+            coefficients.emplace_back(getFloatFromValue(values[j]));
+            LOG(INFO) << "Sensor[" << name << "]'s coefficient[" << j << "]: " << coefficients[j];
+        }
+    } else {
+        LOG(ERROR) << "Sensor[" << name << "] has no Coefficient setting";
+        return false;
+    }
+    if (linked_sensors.size() != coefficients.size()) {
+        LOG(ERROR) << "Sensor[" << name << "] has invalid Coefficient size";
+        return false;
+    }
+    if (!sensor["Offset"].empty()) {
+        offset = sensor["Offset"].asFloat();
+    }
+
+    values = sensor["TriggerSensor"];
+    if (!values.empty()) {
+        if (values.isString()) {
+            trigger_sensors.emplace_back(values.asString());
+            LOG(INFO) << "Sensor[" << name << "]'s TriggerSensor: " << values.asString();
+        } else if (values.size()) {
+            trigger_sensors.reserve(values.size());
+            for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+                if (!values[j].isString()) {
+                    LOG(ERROR) << name << " TriggerSensor should be an array of string";
+                    return false;
+                }
+                trigger_sensors.emplace_back(values[j].asString());
+                LOG(INFO) << "Sensor[" << name << "]'s TriggerSensor[" << j
+                          << "]: " << trigger_sensors[j];
+            }
+        } else {
+            LOG(ERROR) << "Sensor[" << name << "]'s TriggerSensor should be a string";
+            return false;
+        }
+    }
+
+    if (sensor["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
+        formula = FormulaOption::COUNT_THRESHOLD;
+    } else if (sensor["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
+        formula = FormulaOption::WEIGHTED_AVG;
+    } else if (sensor["Formula"].asString().compare("MAXIMUM") == 0) {
+        formula = FormulaOption::MAXIMUM;
+    } else if (sensor["Formula"].asString().compare("MINIMUM") == 0) {
+        formula = FormulaOption::MINIMUM;
+    } else {
+        LOG(ERROR) << "Sensor[" << name << "]'s Formula is invalid";
+        return false;
+    }
+    virtual_sensor_info->reset(new VirtualSensorInfo{
+            linked_sensors, linked_sensors_type, coefficients, offset, trigger_sensors, formula});
+    return true;
+}
+
+bool ParseBindedCdevInfo(const Json::Value &values,
+                         std::unordered_map<std::string, BindedCdevInfo> *binded_cdev_info_map,
+                         const bool support_pid, bool *support_hard_limit) {
+    for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+        Json::Value sub_values;
+        const std::string &cdev_name = values[j]["CdevRequest"].asString();
+        ThrottlingArray cdev_weight_for_pid;
+        cdev_weight_for_pid.fill(NAN);
+        CdevArray cdev_ceiling;
+        cdev_ceiling.fill(std::numeric_limits<int>::max());
+        int max_release_step = std::numeric_limits<int>::max();
+        int max_throttle_step = std::numeric_limits<int>::max();
+        if (support_pid) {
+            if (!values[j]["CdevWeightForPID"].empty()) {
+                LOG(INFO) << "Star to parse " << cdev_name << "'s CdevWeightForPID";
+                if (!getFloatFromJsonValues(values[j]["CdevWeightForPID"], &cdev_weight_for_pid,
+                                            false, false)) {
+                    LOG(ERROR) << "Failed to parse CdevWeightForPID";
+                    binded_cdev_info_map->clear();
+                    return false;
+                }
+            }
+            if (!values[j]["CdevCeiling"].empty()) {
+                LOG(INFO) << "Start to parse CdevCeiling: " << cdev_name;
+                if (!getIntFromJsonValues(values[j]["CdevCeiling"], &cdev_ceiling, false, false)) {
+                    LOG(ERROR) << "Failed to parse CdevCeiling";
+                    binded_cdev_info_map->clear();
+                    return false;
+                }
+            }
+
+            if (!values[j]["MaxReleaseStep"].empty()) {
+                max_release_step = getIntFromValue(values[j]["MaxReleaseStep"]);
+                if (max_release_step < 0) {
+                    LOG(ERROR) << cdev_name << " MaxReleaseStep: " << max_release_step;
+                    binded_cdev_info_map->clear();
+                    return false;
+                } else {
+                    LOG(INFO) << cdev_name << " MaxReleaseStep: " << max_release_step;
+                }
+            }
+            if (!values[j]["MaxThrottleStep"].empty()) {
+                max_throttle_step = getIntFromValue(values[j]["MaxThrottleStep"]);
+                if (max_throttle_step < 0) {
+                    LOG(ERROR) << cdev_name << " MaxThrottleStep: " << max_throttle_step;
+                    binded_cdev_info_map->clear();
+                    return false;
+                } else {
+                    LOG(INFO) << cdev_name << " MaxThrottleStep: " << max_throttle_step;
+                }
+            }
+        }
+        CdevArray limit_info;
+        limit_info.fill(0);
+        ThrottlingArray power_thresholds;
+        power_thresholds.fill(NAN);
+        ReleaseLogic release_logic = ReleaseLogic::NONE;
+
+        sub_values = values[j]["LimitInfo"];
+        if (sub_values.size()) {
+            LOG(INFO) << "Start to parse LimitInfo: " << cdev_name;
+            if (!getIntFromJsonValues(sub_values, &limit_info, false, false)) {
+                LOG(ERROR) << "Failed to parse LimitInfo";
+                binded_cdev_info_map->clear();
+                return false;
+            }
+            *support_hard_limit = true;
+        }
+        // Parse linked power info
+        std::string power_rail;
+        bool high_power_check = false;
+        bool throttling_with_power_link = false;
+        CdevArray cdev_floor_with_power_link;
+        cdev_floor_with_power_link.fill(0);
+
+        const bool power_link_disabled =
+                ::android::base::GetBoolProperty(kPowerLinkDisabledProperty.data(), false);
+        if (!power_link_disabled) {
+            power_rail = values[j]["BindedPowerRail"].asString();
+
+            if (values[j]["HighPowerCheck"].asBool()) {
+                high_power_check = true;
+            }
+            LOG(INFO) << "Highpowercheck: " << std::boolalpha << high_power_check;
+
+            if (values[j]["ThrottlingWithPowerLink"].asBool()) {
+                throttling_with_power_link = true;
+            }
+            LOG(INFO) << "ThrottlingwithPowerLink: " << std::boolalpha
+                      << throttling_with_power_link;
+
+            sub_values = values[j]["CdevFloorWithPowerLink"];
+            if (sub_values.size()) {
+                LOG(INFO) << "Start to parse " << cdev_name << "'s CdevFloorWithPowerLink";
+                if (!getIntFromJsonValues(sub_values, &cdev_floor_with_power_link, false, false)) {
+                    LOG(ERROR) << "Failed to parse CdevFloor";
+                    binded_cdev_info_map->clear();
+                    return false;
+                }
+            }
+            sub_values = values[j]["PowerThreshold"];
+            if (sub_values.size()) {
+                LOG(INFO) << "Start to parse " << cdev_name << "'s PowerThreshold";
+                if (!getFloatFromJsonValues(sub_values, &power_thresholds, false, false)) {
+                    LOG(ERROR) << "Failed to parse power thresholds";
+                    binded_cdev_info_map->clear();
+                    return false;
+                }
+                if (values[j]["ReleaseLogic"].asString() == "INCREASE") {
+                    release_logic = ReleaseLogic::INCREASE;
+                    LOG(INFO) << "Release logic: INCREASE";
+                } else if (values[j]["ReleaseLogic"].asString() == "DECREASE") {
+                    release_logic = ReleaseLogic::DECREASE;
+                    LOG(INFO) << "Release logic: DECREASE";
+                } else if (values[j]["ReleaseLogic"].asString() == "STEPWISE") {
+                    release_logic = ReleaseLogic::STEPWISE;
+                    LOG(INFO) << "Release logic: STEPWISE";
+                } else if (values[j]["ReleaseLogic"].asString() == "RELEASE_TO_FLOOR") {
+                    release_logic = ReleaseLogic::RELEASE_TO_FLOOR;
+                    LOG(INFO) << "Release logic: RELEASE_TO_FLOOR";
+                } else {
+                    LOG(ERROR) << "Release logic is invalid";
+                    binded_cdev_info_map->clear();
+                    return false;
+                }
+            }
+        }
+
+        (*binded_cdev_info_map)[cdev_name] = {
+                .limit_info = limit_info,
+                .power_thresholds = power_thresholds,
+                .release_logic = release_logic,
+                .high_power_check = high_power_check,
+                .throttling_with_power_link = throttling_with_power_link,
+                .cdev_weight_for_pid = cdev_weight_for_pid,
+                .cdev_ceiling = cdev_ceiling,
+                .max_release_step = max_release_step,
+                .max_throttle_step = max_throttle_step,
+                .cdev_floor_with_power_link = cdev_floor_with_power_link,
+                .power_rail = power_rail,
+        };
+    }
+    return true;
+}
+
+bool ParseSensorThrottlingInfo(const std::string_view name, const Json::Value &sensor,
+                               bool *support_throttling,
+                               std::shared_ptr<ThrottlingInfo> *throttling_info) {
+    std::array<float, kThrottlingSeverityCount> k_po;
+    k_po.fill(0.0);
+    std::array<float, kThrottlingSeverityCount> k_pu;
+    k_pu.fill(0.0);
+    std::array<float, kThrottlingSeverityCount> k_i;
+    k_i.fill(0.0);
+    std::array<float, kThrottlingSeverityCount> k_d;
+    k_d.fill(0.0);
+    std::array<float, kThrottlingSeverityCount> i_max;
+    i_max.fill(NAN);
+    std::array<float, kThrottlingSeverityCount> max_alloc_power;
+    max_alloc_power.fill(NAN);
+    std::array<float, kThrottlingSeverityCount> min_alloc_power;
+    min_alloc_power.fill(NAN);
+    std::array<float, kThrottlingSeverityCount> s_power;
+    s_power.fill(NAN);
+    std::array<float, kThrottlingSeverityCount> i_cutoff;
+    i_cutoff.fill(NAN);
+    float i_default = 0.0;
+    int tran_cycle = 0;
+    bool support_pid = false;
+    bool support_hard_limit = false;
+
+    // Parse PID parameters
+    if (!sensor["PIDInfo"].empty()) {
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s K_Po";
+        if (sensor["PIDInfo"]["K_Po"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["K_Po"], &k_po, false, false)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_Po";
+            return false;
+        }
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s  K_Pu";
+        if (sensor["PIDInfo"]["K_Pu"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["K_Pu"], &k_pu, false, false)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_Pu";
+            return false;
+        }
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s K_I";
+        if (sensor["PIDInfo"]["K_I"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["K_I"], &k_i, false, false)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_I";
+            return false;
+        }
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s K_D";
+        if (sensor["PIDInfo"]["K_D"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["K_D"], &k_d, false, false)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse K_D";
+            return false;
+        }
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s I_Max";
+        if (sensor["PIDInfo"]["I_Max"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["I_Max"], &i_max, false, false)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse I_Max";
+            return false;
+        }
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s MaxAllocPower";
+        if (sensor["PIDInfo"]["MaxAllocPower"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["MaxAllocPower"], &max_alloc_power, false,
+                                    true)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse MaxAllocPower";
+            return false;
+        }
+        LOG(INFO) << "Start to parse"
+                  << " Sensor[" << name << "]'s MinAllocPower";
+        if (sensor["PIDInfo"]["MinAllocPower"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["MinAllocPower"], &min_alloc_power, false,
+                                    true)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse MinAllocPower";
+            return false;
+        }
+        LOG(INFO) << "Start to parse Sensor[" << name << "]'s S_Power";
+        if (sensor["PIDInfo"]["S_Power"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["S_Power"], &s_power, false, true)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse S_Power";
+            return false;
+        }
+        LOG(INFO) << "Start to parse Sensor[" << name << "]'s I_Cutoff";
+        if (sensor["PIDInfo"]["I_Cutoff"].empty() ||
+            !getFloatFromJsonValues(sensor["PIDInfo"]["I_Cutoff"], &i_cutoff, false, false)) {
+            LOG(ERROR) << "Sensor[" << name << "]: Failed to parse I_Cutoff";
+            return false;
+        }
+        i_default = getFloatFromValue(sensor["PIDInfo"]["I_Default"]);
+        LOG(INFO) << "Sensor[" << name << "]'s I_Default: " << i_default;
+
+        tran_cycle = getFloatFromValue(sensor["PIDInfo"]["TranCycle"]);
+        LOG(INFO) << "Sensor[" << name << "]'s TranCycle: " << tran_cycle;
+
+        // Confirm we have at least one valid PID combination
+        bool valid_pid_combination = false;
+        for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+            if (!std::isnan(s_power[j])) {
+                if (std::isnan(k_po[j]) || std::isnan(k_pu[j]) || std::isnan(k_i[j]) ||
+                    std::isnan(k_d[j]) || std::isnan(i_max[j]) || std::isnan(max_alloc_power[j]) ||
+                    std::isnan(min_alloc_power[j]) || std::isnan(i_cutoff[j])) {
+                    valid_pid_combination = false;
+                    break;
+                } else {
+                    valid_pid_combination = true;
+                }
+            }
+        }
+        if (!valid_pid_combination) {
+            LOG(ERROR) << "Sensor[" << name << "]: Invalid PID parameters combinations";
+            return false;
+        } else {
+            support_pid = true;
+        }
+    }
+
+    // Parse binded cooling device
+    std::unordered_map<std::string, BindedCdevInfo> binded_cdev_info_map;
+    if (!ParseBindedCdevInfo(sensor["BindedCdevInfo"], &binded_cdev_info_map, support_pid,
+                             &support_hard_limit)) {
+        LOG(ERROR) << "Sensor[" << name << "]: failed to parse BindedCdevInfo";
+        return false;
+    }
+
+    std::unordered_map<std::string, ThrottlingArray> excluded_power_info_map;
+    Json::Value values = sensor["ExcludedPowerInfo"];
+    for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+        Json::Value sub_values;
+        const std::string &power_rail = values[j]["PowerRail"].asString();
+        if (power_rail.empty()) {
+            LOG(ERROR) << "Sensor[" << name << "] failed to parse excluded PowerRail";
+            return false;
+        }
+        ThrottlingArray power_weight;
+        power_weight.fill(1);
+        if (!values[j]["PowerWeight"].empty()) {
+            LOG(INFO) << "Sensor[" << name << "]: Start to parse " << power_rail
+                      << "'s PowerWeight";
+            if (!getFloatFromJsonValues(values[j]["PowerWeight"], &power_weight, false, false)) {
+                LOG(ERROR) << "Failed to parse PowerWeight";
+                return false;
+            }
+        }
+        excluded_power_info_map[power_rail] = power_weight;
+    }
+    throttling_info->reset(new ThrottlingInfo{
+            k_po, k_pu, k_i, k_d, i_max, max_alloc_power, min_alloc_power, s_power, i_cutoff,
+            i_default, tran_cycle, excluded_power_info_map, binded_cdev_info_map});
+    *support_throttling = support_pid | support_hard_limit;
+    return true;
+}
+
+bool ParseSensorInfo(const Json::Value &config,
+                     std::unordered_map<std::string, SensorInfo> *sensors_parsed) {
+    Json::Value sensors = config["Sensors"];
+    std::size_t total_parsed = 0;
+    std::unordered_set<std::string> sensors_name_parsed;
+
+    for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) {
+        const std::string &name = sensors[i]["Name"].asString();
+        LOG(INFO) << "Sensor[" << i << "]'s Name: " << name;
+        if (name.empty()) {
+            LOG(ERROR) << "Failed to read Sensor[" << i << "]'s Name";
+            sensors_parsed->clear();
+            return false;
+        }
+
+        auto result = sensors_name_parsed.insert(name);
+        if (!result.second) {
+            LOG(ERROR) << "Duplicate Sensor[" << i << "]'s Name";
+            sensors_parsed->clear();
+            return false;
+        }
+
+        std::string sensor_type_str = sensors[i]["Type"].asString();
+        LOG(INFO) << "Sensor[" << name << "]'s Type: " << sensor_type_str;
+        TemperatureType sensor_type;
+
+        if (!getTypeFromString(sensor_type_str, &sensor_type)) {
+            LOG(ERROR) << "Invalid Sensor[" << name << "]'s Type: " << sensor_type_str;
+            sensors_parsed->clear();
+            return false;
+        }
+
+        bool send_cb = false;
+        if (!sensors[i]["Monitor"].empty() && sensors[i]["Monitor"].isBool()) {
+            send_cb = sensors[i]["Monitor"].asBool();
+        } else if (!sensors[i]["SendCallback"].empty() && sensors[i]["SendCallback"].isBool()) {
+            send_cb = sensors[i]["SendCallback"].asBool();
+        }
+        LOG(INFO) << "Sensor[" << name << "]'s SendCallback: " << std::boolalpha << send_cb
+                  << std::noboolalpha;
+
+        bool send_powerhint = false;
+        if (sensors[i]["SendPowerHint"].empty() || !sensors[i]["SendPowerHint"].isBool()) {
+            LOG(INFO) << "Failed to read Sensor[" << name << "]'s SendPowerHint, set to 'false'";
+        } else if (sensors[i]["SendPowerHint"].asBool()) {
+            send_powerhint = true;
+        }
+        LOG(INFO) << "Sensor[" << name << "]'s SendPowerHint: " << std::boolalpha << send_powerhint
+                  << std::noboolalpha;
+
+        bool is_hidden = false;
+        if (sensors[i]["Hidden"].empty() || !sensors[i]["Hidden"].isBool()) {
+            LOG(INFO) << "Failed to read Sensor[" << name << "]'s Hidden, set to 'false'";
+        } else if (sensors[i]["Hidden"].asBool()) {
+            is_hidden = true;
+        }
+        LOG(INFO) << "Sensor[" << name << "]'s Hidden: " << std::boolalpha << is_hidden
+                  << std::noboolalpha;
+
+        std::array<float, kThrottlingSeverityCount> hot_thresholds;
+        hot_thresholds.fill(NAN);
+        std::array<float, kThrottlingSeverityCount> cold_thresholds;
+        cold_thresholds.fill(NAN);
+        std::array<float, kThrottlingSeverityCount> hot_hysteresis;
+        hot_hysteresis.fill(0.0);
+        std::array<float, kThrottlingSeverityCount> cold_hysteresis;
+        cold_hysteresis.fill(0.0);
+
+        Json::Value values = sensors[i]["HotThreshold"];
+        if (!values.size()) {
+            LOG(INFO) << "Sensor[" << name << "]'s HotThreshold, default all to NAN";
+        } else if (values.size() != kThrottlingSeverityCount) {
+            LOG(ERROR) << "Invalid Sensor[" << name << "]'s HotThreshold count:" << values.size();
+            sensors_parsed->clear();
+            return false;
+        } else {
+            float min = std::numeric_limits<float>::min();
+            for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+                hot_thresholds[j] = getFloatFromValue(values[j]);
+                if (!std::isnan(hot_thresholds[j])) {
+                    if (hot_thresholds[j] < min) {
+                        LOG(ERROR) << "Invalid "
+                                   << "Sensor[" << name << "]'s HotThreshold[j" << j
+                                   << "]: " << hot_thresholds[j] << " < " << min;
+                        sensors_parsed->clear();
+                        return false;
+                    }
+                    min = hot_thresholds[j];
+                }
+                LOG(INFO) << "Sensor[" << name << "]'s HotThreshold[" << j
+                          << "]: " << hot_thresholds[j];
+            }
+        }
+
+        values = sensors[i]["HotHysteresis"];
+        if (!values.size()) {
+            LOG(INFO) << "Sensor[" << name << "]'s HotHysteresis, default all to 0.0";
+        } else if (values.size() != kThrottlingSeverityCount) {
+            LOG(ERROR) << "Invalid Sensor[" << name << "]'s HotHysteresis, count:" << values.size();
+            sensors_parsed->clear();
+            return false;
+        } else {
+            for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+                hot_hysteresis[j] = getFloatFromValue(values[j]);
+                if (std::isnan(hot_hysteresis[j])) {
+                    LOG(ERROR) << "Invalid Sensor[" << name
+                               << "]'s HotHysteresis: " << hot_hysteresis[j];
+                    sensors_parsed->clear();
+                    return false;
+                }
+                LOG(INFO) << "Sensor[" << name << "]'s HotHysteresis[" << j
+                          << "]: " << hot_hysteresis[j];
+            }
+        }
+
+        for (Json::Value::ArrayIndex j = 0; j < (kThrottlingSeverityCount - 1); ++j) {
+            if (std::isnan(hot_thresholds[j])) {
+                continue;
+            }
+            for (auto k = j + 1; k < kThrottlingSeverityCount; ++k) {
+                if (std::isnan(hot_thresholds[k])) {
+                    continue;
+                } else if (hot_thresholds[j] > (hot_thresholds[k] - hot_hysteresis[k])) {
+                    LOG(ERROR) << "Sensor[" << name << "]'s hot threshold " << j
+                               << " is overlapped";
+                    sensors_parsed->clear();
+                    return false;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        values = sensors[i]["ColdThreshold"];
+        if (!values.size()) {
+            LOG(INFO) << "Sensor[" << name << "]'s ColdThreshold, default all to NAN";
+        } else if (values.size() != kThrottlingSeverityCount) {
+            LOG(ERROR) << "Invalid Sensor[" << name << "]'s ColdThreshold count:" << values.size();
+            sensors_parsed->clear();
+            return false;
+        } else {
+            float max = std::numeric_limits<float>::max();
+            for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+                cold_thresholds[j] = getFloatFromValue(values[j]);
+                if (!std::isnan(cold_thresholds[j])) {
+                    if (cold_thresholds[j] > max) {
+                        LOG(ERROR) << "Invalid "
+                                   << "Sensor[" << name << "]'s ColdThreshold[j" << j
+                                   << "]: " << cold_thresholds[j] << " > " << max;
+                        sensors_parsed->clear();
+                        return false;
+                    }
+                    max = cold_thresholds[j];
+                }
+                LOG(INFO) << "Sensor[" << name << "]'s ColdThreshold[" << j
+                          << "]: " << cold_thresholds[j];
+            }
+        }
+
+        values = sensors[i]["ColdHysteresis"];
+        if (!values.size()) {
+            LOG(INFO) << "Sensor[" << name << "]'s ColdHysteresis, default all to 0.0";
+        } else if (values.size() != kThrottlingSeverityCount) {
+            LOG(ERROR) << "Invalid Sensor[" << name << "]'s ColdHysteresis count:" << values.size();
+            sensors_parsed->clear();
+            return false;
+        } else {
+            for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) {
+                cold_hysteresis[j] = getFloatFromValue(values[j]);
+                if (std::isnan(cold_hysteresis[j])) {
+                    LOG(ERROR) << "Invalid Sensor[" << name
+                               << "]'s ColdHysteresis: " << cold_hysteresis[j];
+                    sensors_parsed->clear();
+                    return false;
+                }
+                LOG(INFO) << "Sensor[" << name << "]'s ColdHysteresis[" << j
+                          << "]: " << cold_hysteresis[j];
+            }
+        }
+
+        for (Json::Value::ArrayIndex j = 0; j < (kThrottlingSeverityCount - 1); ++j) {
+            if (std::isnan(cold_thresholds[j])) {
+                continue;
+            }
+            for (auto k = j + 1; k < kThrottlingSeverityCount; ++k) {
+                if (std::isnan(cold_thresholds[k])) {
+                    continue;
+                } else if (cold_thresholds[j] < (cold_thresholds[k] + cold_hysteresis[k])) {
+                    LOG(ERROR) << "Sensor[" << name << "]'s cold threshold " << j
+                               << " is overlapped";
+                    sensors_parsed->clear();
+                    return false;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        std::string temp_path;
+        if (!sensors[i]["TempPath"].empty()) {
+            temp_path = sensors[i]["TempPath"].asString();
+            LOG(INFO) << "Sensor[" << name << "]'s TempPath: " << temp_path;
+        }
+
+        float vr_threshold = NAN;
+        if (!sensors[i]["VrThreshold"].empty()) {
+            vr_threshold = getFloatFromValue(sensors[i]["VrThreshold"]);
+            LOG(INFO) << "Sensor[" << name << "]'s VrThreshold: " << vr_threshold;
+        }
+        float multiplier = sensors[i]["Multiplier"].asFloat();
+        LOG(INFO) << "Sensor[" << name << "]'s Multiplier: " << multiplier;
+
+        std::chrono::milliseconds polling_delay = kUeventPollTimeoutMs;
+        if (!sensors[i]["PollingDelay"].empty()) {
+            const auto value = getIntFromValue(sensors[i]["PollingDelay"]);
+            polling_delay = (value > 0) ? std::chrono::milliseconds(value)
+                                        : std::chrono::milliseconds::max();
+        }
+        LOG(INFO) << "Sensor[" << name << "]'s Polling delay: " << polling_delay.count();
+
+        std::chrono::milliseconds passive_delay = kMinPollIntervalMs;
+        if (!sensors[i]["PassiveDelay"].empty()) {
+            const auto value = getIntFromValue(sensors[i]["PassiveDelay"]);
+            passive_delay = (value > 0) ? std::chrono::milliseconds(value)
+                                        : std::chrono::milliseconds::max();
+        }
+        LOG(INFO) << "Sensor[" << name << "]'s Passive delay: " << passive_delay.count();
+
+        std::chrono::milliseconds time_resolution;
+        if (sensors[i]["TimeResolution"].empty()) {
+            time_resolution = kMinPollIntervalMs;
+        } else {
+            time_resolution =
+                    std::chrono::milliseconds(getIntFromValue(sensors[i]["TimeResolution"]));
+        }
+        LOG(INFO) << "Sensor[" << name << "]'s Time resolution: " << time_resolution.count();
+
+        if (is_hidden && send_cb) {
+            LOG(ERROR) << "is_hidden and send_cb cannot be enabled together";
+            sensors_parsed->clear();
+            return false;
+        }
+
+        std::unique_ptr<VirtualSensorInfo> virtual_sensor_info;
+        if (!ParseVirtualSensorInfo(name, sensors[i], &virtual_sensor_info)) {
+            LOG(ERROR) << "Sensor[" << name << "]: failed to parse virtual sensor info";
+            sensors_parsed->clear();
+            return false;
+        }
+
+        bool support_throttling = false;  // support pid or hard limit
+        std::shared_ptr<ThrottlingInfo> throttling_info;
+        if (!ParseSensorThrottlingInfo(name, sensors[i], &support_throttling, &throttling_info)) {
+            LOG(ERROR) << "Sensor[" << name << "]: failed to parse throttling info";
+            sensors_parsed->clear();
+            return false;
+        }
+
+        bool is_watch = (send_cb | send_powerhint | support_throttling);
+        LOG(INFO) << "Sensor[" << name << "]'s is_watch: " << std::boolalpha << is_watch;
+
+        (*sensors_parsed)[name] = {
+                .type = sensor_type,
+                .hot_thresholds = hot_thresholds,
+                .cold_thresholds = cold_thresholds,
+                .hot_hysteresis = hot_hysteresis,
+                .cold_hysteresis = cold_hysteresis,
+                .temp_path = temp_path,
+                .vr_threshold = vr_threshold,
+                .multiplier = multiplier,
+                .polling_delay = polling_delay,
+                .passive_delay = passive_delay,
+                .time_resolution = time_resolution,
+                .send_cb = send_cb,
+                .send_powerhint = send_powerhint,
+                .is_watch = is_watch,
+                .is_hidden = is_hidden,
+                .virtual_sensor_info = std::move(virtual_sensor_info),
+                .throttling_info = std::move(throttling_info),
+        };
+
+        ++total_parsed;
+    }
+    LOG(INFO) << total_parsed << " Sensors parsed successfully";
+    return true;
+}
+
+bool ParseCoolingDevice(const Json::Value &config,
+                        std::unordered_map<std::string, CdevInfo> *cooling_devices_parsed) {
+    Json::Value cooling_devices = config["CoolingDevices"];
+    std::size_t total_parsed = 0;
+    std::unordered_set<std::string> cooling_devices_name_parsed;
+
+    for (Json::Value::ArrayIndex i = 0; i < cooling_devices.size(); ++i) {
+        const std::string &name = cooling_devices[i]["Name"].asString();
+        LOG(INFO) << "CoolingDevice[" << i << "]'s Name: " << name;
+        if (name.empty()) {
+            LOG(ERROR) << "Failed to read CoolingDevice[" << i << "]'s Name";
+            cooling_devices_parsed->clear();
+            return false;
+        }
+
+        auto result = cooling_devices_name_parsed.insert(name.data());
+        if (!result.second) {
+            LOG(ERROR) << "Duplicate CoolingDevice[" << i << "]'s Name";
+            cooling_devices_parsed->clear();
+            return false;
+        }
+
+        std::string cooling_device_type_str = cooling_devices[i]["Type"].asString();
+        LOG(INFO) << "CoolingDevice[" << name << "]'s Type: " << cooling_device_type_str;
+        CoolingType cooling_device_type;
+
+        if (!getTypeFromString(cooling_device_type_str, &cooling_device_type)) {
+            LOG(ERROR) << "Invalid CoolingDevice[" << name
+                       << "]'s Type: " << cooling_device_type_str;
+            cooling_devices_parsed->clear();
+            return false;
+        }
+
+        const std::string &read_path = cooling_devices[i]["ReadPath"].asString();
+        LOG(INFO) << "Cdev Read Path: " << (read_path.empty() ? "default" : read_path);
+
+        const std::string &write_path = cooling_devices[i]["WritePath"].asString();
+        LOG(INFO) << "Cdev Write Path: " << (write_path.empty() ? "default" : write_path);
+
+        std::vector<float> state2power;
+        Json::Value values = cooling_devices[i]["State2Power"];
+        if (values.size()) {
+            state2power.reserve(values.size());
+            for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+                state2power.emplace_back(getFloatFromValue(values[j]));
+                LOG(INFO) << "Cooling device[" << name << "]'s Power2State[" << j
+                          << "]: " << state2power[j];
+            }
+        } else {
+            LOG(INFO) << "CoolingDevice[" << i << "]'s Name: " << name
+                      << " does not support State2Power";
+        }
+
+        const std::string &power_rail = cooling_devices[i]["PowerRail"].asString();
+        LOG(INFO) << "Cooling device power rail : " << power_rail;
+
+        (*cooling_devices_parsed)[name] = {
+                .type = cooling_device_type,
+                .read_path = read_path,
+                .write_path = write_path,
+                .state2power = state2power,
+        };
+        ++total_parsed;
+    }
+    LOG(INFO) << total_parsed << " CoolingDevices parsed successfully";
+    return true;
+}
+
+bool ParsePowerRailInfo(const Json::Value &config,
+                        std::unordered_map<std::string, PowerRailInfo> *power_rails_parsed) {
+    Json::Value power_rails = config["PowerRails"];
+    std::size_t total_parsed = 0;
+    std::unordered_set<std::string> power_rails_name_parsed;
+
+    for (Json::Value::ArrayIndex i = 0; i < power_rails.size(); ++i) {
+        const std::string &name = power_rails[i]["Name"].asString();
+        LOG(INFO) << "PowerRail[" << i << "]'s Name: " << name;
+        if (name.empty()) {
+            LOG(ERROR) << "Failed to read PowerRail[" << i << "]'s Name";
+            power_rails_parsed->clear();
+            return false;
+        }
+
+        std::string rail;
+        if (power_rails[i]["Rail"].empty()) {
+            rail = name;
+        } else {
+            rail = power_rails[i]["Rail"].asString();
+        }
+        LOG(INFO) << "PowerRail[" << i << "]'s Rail: " << rail;
+
+        std::vector<std::string> linked_power_rails;
+        std::vector<float> coefficients;
+        float offset = 0;
+        FormulaOption formula = FormulaOption::COUNT_THRESHOLD;
+        bool is_virtual_power_rail = false;
+        Json::Value values;
+        int power_sample_count = 0;
+        std::chrono::milliseconds power_sample_delay;
+
+        if (!power_rails[i]["VirtualRails"].empty() && power_rails[i]["VirtualRails"].isBool()) {
+            is_virtual_power_rail = power_rails[i]["VirtualRails"].asBool();
+            LOG(INFO) << "PowerRails[" << name << "]'s VirtualRail, set to 'true'";
+        }
+
+        if (is_virtual_power_rail) {
+            values = power_rails[i]["Combination"];
+            if (values.size()) {
+                linked_power_rails.reserve(values.size());
+                for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+                    linked_power_rails.emplace_back(values[j].asString());
+                    LOG(INFO) << "PowerRail[" << name << "]'s combination[" << j
+                              << "]: " << linked_power_rails[j];
+                }
+            } else {
+                LOG(ERROR) << "PowerRails[" << name << "] has no combination for VirtualRail";
+                power_rails_parsed->clear();
+                return false;
+            }
+
+            values = power_rails[i]["Coefficient"];
+            if (values.size()) {
+                coefficients.reserve(values.size());
+                for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+                    coefficients.emplace_back(getFloatFromValue(values[j]));
+                    LOG(INFO) << "PowerRail[" << name << "]'s coefficient[" << j
+                              << "]: " << coefficients[j];
+                }
+            } else {
+                LOG(ERROR) << "PowerRails[" << name << "] has no coefficient for VirtualRail";
+                power_rails_parsed->clear();
+                return false;
+            }
+
+            if (linked_power_rails.size() != coefficients.size()) {
+                LOG(ERROR) << "PowerRails[" << name
+                           << "]'s combination size is not matched with coefficient size";
+                power_rails_parsed->clear();
+                return false;
+            }
+
+            if (!power_rails[i]["Offset"].empty()) {
+                offset = power_rails[i]["Offset"].asFloat();
+            }
+
+            if (linked_power_rails.size() != coefficients.size()) {
+                LOG(ERROR) << "PowerRails[" << name
+                           << "]'s combination size is not matched with coefficient size";
+                power_rails_parsed->clear();
+                return false;
+            }
+
+            if (power_rails[i]["Formula"].asString().compare("COUNT_THRESHOLD") == 0) {
+                formula = FormulaOption::COUNT_THRESHOLD;
+            } else if (power_rails[i]["Formula"].asString().compare("WEIGHTED_AVG") == 0) {
+                formula = FormulaOption::WEIGHTED_AVG;
+            } else if (power_rails[i]["Formula"].asString().compare("MAXIMUM") == 0) {
+                formula = FormulaOption::MAXIMUM;
+            } else if (power_rails[i]["Formula"].asString().compare("MINIMUM") == 0) {
+                formula = FormulaOption::MINIMUM;
+            } else {
+                LOG(ERROR) << "PowerRails[" << name << "]'s Formula is invalid";
+                power_rails_parsed->clear();
+                return false;
+            }
+        }
+
+        std::unique_ptr<VirtualPowerRailInfo> virtual_power_rail_info;
+        if (is_virtual_power_rail) {
+            virtual_power_rail_info.reset(
+                    new VirtualPowerRailInfo{linked_power_rails, coefficients, offset, formula});
+        }
+
+        power_sample_count = power_rails[i]["PowerSampleCount"].asInt();
+        LOG(INFO) << "Power sample Count: " << power_sample_count;
+
+        if (!power_rails[i]["PowerSampleDelay"]) {
+            power_sample_delay = std::chrono::milliseconds::max();
+        } else {
+            power_sample_delay =
+                    std::chrono::milliseconds(getIntFromValue(power_rails[i]["PowerSampleDelay"]));
+        }
+
+        (*power_rails_parsed)[name] = {
+                .rail = rail,
+                .power_sample_count = power_sample_count,
+                .power_sample_delay = power_sample_delay,
+                .virtual_power_rail_info = std::move(virtual_power_rail_info),
+        };
+        ++total_parsed;
+    }
+    LOG(INFO) << total_parsed << " PowerRails parsed successfully";
+    return true;
+}
+
+template <typename T, typename U>
+bool ParseStatsInfo(const Json::Value &stats_config,
+                    const std::unordered_map<std::string, U> &entity_info, StatsInfo<T> *stats_info,
+                    T min_value) {
+    if (stats_config.empty()) {
+        LOG(INFO) << "No stats config";
+        return true;
+    }
+    std::variant<bool, std::unordered_set<std::string>>
+            record_by_default_threshold_all_or_name_set_ = false;
+    if (stats_config["DefaultThresholdEnableAll"].empty() ||
+        !stats_config["DefaultThresholdEnableAll"].isBool()) {
+        LOG(INFO) << "Failed to read stats DefaultThresholdEnableAll, set to 'false'";
+    } else if (stats_config["DefaultThresholdEnableAll"].asBool()) {
+        record_by_default_threshold_all_or_name_set_ = true;
+    }
+    LOG(INFO) << "DefaultThresholdEnableAll " << std::boolalpha
+              << std::get<bool>(record_by_default_threshold_all_or_name_set_) << std::noboolalpha;
+
+    Json::Value values = stats_config["RecordWithDefaultThreshold"];
+    if (values.size()) {
+        if (std::get<bool>(record_by_default_threshold_all_or_name_set_)) {
+            LOG(ERROR) << "Cannot enable record with default threshold when "
+                          "DefaultThresholdEnableAll true.";
+            return false;
+        }
+        record_by_default_threshold_all_or_name_set_ = std::unordered_set<std::string>();
+        for (Json::Value::ArrayIndex i = 0; i < values.size(); ++i) {
+            std::string name = values[i].asString();
+            if (!entity_info.count(name)) {
+                LOG(ERROR) << "Unknown name [" << name << "] not present in entity_info.";
+                return false;
+            }
+            std::get<std::unordered_set<std::string>>(record_by_default_threshold_all_or_name_set_)
+                    .insert(name);
+        }
+    } else {
+        LOG(INFO) << "No stat by default threshold enabled.";
+    }
+
+    std::unordered_map<std::string, std::vector<ThresholdList<T>>> record_by_threshold;
+    values = stats_config["RecordWithThreshold"];
+    if (values.size()) {
+        Json::Value threshold_values;
+        for (Json::Value::ArrayIndex i = 0; i < values.size(); i++) {
+            const std::string &name = values[i]["Name"].asString();
+            if (!entity_info.count(name)) {
+                LOG(ERROR) << "Unknown name [" << name << "] not present in entity_info.";
+                return false;
+            }
+
+            std::optional<std::string> logging_name;
+            if (!values[i]["LoggingName"].empty()) {
+                logging_name = values[i]["LoggingName"].asString();
+                LOG(INFO) << "For [" << name << "]"
+                          << ", stats logging name is [" << logging_name.value() << "]";
+            }
+
+            LOG(INFO) << "Start to parse stats threshold for [" << name << "]";
+            threshold_values = values[i]["Thresholds"];
+            if (threshold_values.empty()) {
+                LOG(ERROR) << "Empty stats threshold not valid.";
+                return false;
+            }
+            const auto &threshold_values_count = threshold_values.size();
+            if (threshold_values_count > kMaxStatsThresholdCount) {
+                LOG(ERROR) << "Number of stats threshold " << threshold_values_count
+                           << " greater than max " << kMaxStatsThresholdCount;
+                return false;
+            }
+            std::vector<T> stats_threshold(threshold_values_count);
+            T prev_value = min_value;
+            LOG(INFO) << "Thresholds:";
+            for (Json::Value::ArrayIndex i = 0; i < threshold_values_count; ++i) {
+                stats_threshold[i] = std::is_floating_point_v<T>
+                                             ? getFloatFromValue(threshold_values[i])
+                                             : getIntFromValue(threshold_values[i]);
+                if (stats_threshold[i] <= prev_value) {
+                    LOG(ERROR) << "Invalid array[" << i << "]" << stats_threshold[i]
+                               << " is <=" << prev_value;
+                    return false;
+                }
+                prev_value = stats_threshold[i];
+                LOG(INFO) << "[" << i << "]: " << stats_threshold[i];
+            }
+            record_by_threshold[name].emplace_back(logging_name, stats_threshold);
+        }
+    } else {
+        LOG(INFO) << "No stat by threshold enabled.";
+    }
+
+    (*stats_info) = {.record_by_default_threshold_all_or_name_set_ =
+                             record_by_default_threshold_all_or_name_set_,
+                     .record_by_threshold = record_by_threshold};
+    return true;
+}
+
+bool ParseStatsConfig(const Json::Value &config,
+                      const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+                      const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
+                      StatsConfig *stats_config_parsed) {
+    Json::Value stats_config = config["Stats"];
+
+    if (stats_config.empty()) {
+        LOG(INFO) << "No Stats Config present.";
+        return true;
+    }
+
+    LOG(INFO) << "Parse Stats Config for Sensor Temp.";
+    // Parse sensor stats config
+    if (!ParseStatsInfo(stats_config["Sensors"], sensor_info_map_,
+                        &stats_config_parsed->sensor_stats_info,
+                        std::numeric_limits<float>::lowest())) {
+        LOG(ERROR) << "Failed to parse sensor temp stats info.";
+        stats_config_parsed->clear();
+        return false;
+    }
+
+    // Parse cooling device user vote
+    if (stats_config["CoolingDevices"].empty()) {
+        LOG(INFO) << "No cooling device stats present.";
+        return true;
+    }
+
+    LOG(INFO) << "Parse Stats Config for Sensor CDev Request.";
+    if (!ParseStatsInfo(stats_config["CoolingDevices"]["RecordVotePerSensor"],
+                        cooling_device_info_map_, &stats_config_parsed->cooling_device_request_info,
+                        -1)) {
+        LOG(ERROR) << "Failed to parse cooling device user vote stats info.";
+        stats_config_parsed->clear();
+        return false;
+    }
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_info.h b/aidl/thermal/utils/thermal_info.h
new file mode 100644 (file)
index 0000000..5b2e8b8
--- /dev/null
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/thermal/CoolingType.h>
+#include <aidl/android/hardware/thermal/TemperatureType.h>
+#include <aidl/android/hardware/thermal/ThrottlingSeverity.h>
+#include <json/value.h>
+
+#include <chrono>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <variant>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr size_t kThrottlingSeverityCount =
+        std::distance(::ndk::enum_range<ThrottlingSeverity>().begin(),
+                      ::ndk::enum_range<ThrottlingSeverity>().end());
+using ThrottlingArray = std::array<float, static_cast<size_t>(kThrottlingSeverityCount)>;
+using CdevArray = std::array<int, static_cast<size_t>(kThrottlingSeverityCount)>;
+constexpr std::chrono::milliseconds kMinPollIntervalMs = std::chrono::milliseconds(2000);
+constexpr std::chrono::milliseconds kUeventPollTimeoutMs = std::chrono::milliseconds(300000);
+// Max number of time_in_state buckets is 20 in atoms
+// VendorSensorCoolingDeviceStats, VendorTempResidencyStats
+constexpr int kMaxStatsResidencyCount = 20;
+constexpr int kMaxStatsThresholdCount = kMaxStatsResidencyCount - 1;
+
+enum FormulaOption : uint32_t {
+    COUNT_THRESHOLD = 0,
+    WEIGHTED_AVG,
+    MAXIMUM,
+    MINIMUM,
+};
+
+template <typename T>
+struct ThresholdList {
+    std::optional<std::string> logging_name;
+    std::vector<T> thresholds;
+    explicit ThresholdList(std::optional<std::string> logging_name, std::vector<T> thresholds)
+        : logging_name(logging_name), thresholds(thresholds) {}
+
+    ThresholdList() = default;
+    ThresholdList(const ThresholdList &) = default;
+    ThresholdList &operator=(const ThresholdList &) = default;
+    ThresholdList(ThresholdList &&) = default;
+    ThresholdList &operator=(ThresholdList &&) = default;
+    ~ThresholdList() = default;
+};
+
+template <typename T>
+struct StatsInfo {
+    // if bool, record all or none depending on flag
+    // if set, check name present in set
+    std::variant<bool, std::unordered_set<std::string> >
+            record_by_default_threshold_all_or_name_set_;
+    // map name to list of thresholds
+    std::unordered_map<std::string, std::vector<ThresholdList<T> > > record_by_threshold;
+    void clear() {
+        record_by_default_threshold_all_or_name_set_ = false;
+        record_by_threshold.clear();
+    }
+};
+
+struct StatsConfig {
+    StatsInfo<float> sensor_stats_info;
+    StatsInfo<int> cooling_device_request_info;
+    void clear() {
+        sensor_stats_info.clear();
+        cooling_device_request_info.clear();
+    }
+};
+
+enum SensorFusionType : uint32_t {
+    SENSOR = 0,
+    ODPM,
+};
+
+struct VirtualSensorInfo {
+    std::vector<std::string> linked_sensors;
+    std::vector<SensorFusionType> linked_sensors_type;
+    std::vector<float> coefficients;
+    float offset;
+    std::vector<std::string> trigger_sensors;
+    FormulaOption formula;
+};
+
+struct VirtualPowerRailInfo {
+    std::vector<std::string> linked_power_rails;
+    std::vector<float> coefficients;
+    float offset;
+    FormulaOption formula;
+};
+
+// The method when the ODPM power is lower than threshold
+enum ReleaseLogic : uint32_t {
+    INCREASE = 0,      // Increase throttling by step
+    DECREASE,          // Decrease throttling by step
+    STEPWISE,          // Support both increase and decrease logix
+    RELEASE_TO_FLOOR,  // Release throttling to floor directly
+    NONE,
+};
+
+struct BindedCdevInfo {
+    CdevArray limit_info;
+    ThrottlingArray power_thresholds;
+    ReleaseLogic release_logic;
+    ThrottlingArray cdev_weight_for_pid;
+    CdevArray cdev_ceiling;
+    int max_release_step;
+    int max_throttle_step;
+    CdevArray cdev_floor_with_power_link;
+    std::string power_rail;
+    // The flag for activate release logic when power is higher than power threshold
+    bool high_power_check;
+    // The flag for only triggering throttling until all power samples are collected
+    bool throttling_with_power_link;
+};
+
+struct ThrottlingInfo {
+    ThrottlingArray k_po;
+    ThrottlingArray k_pu;
+    ThrottlingArray k_i;
+    ThrottlingArray k_d;
+    ThrottlingArray i_max;
+    ThrottlingArray max_alloc_power;
+    ThrottlingArray min_alloc_power;
+    ThrottlingArray s_power;
+    ThrottlingArray i_cutoff;
+    float i_default;
+    int tran_cycle;
+    std::unordered_map<std::string, ThrottlingArray> excluded_power_info_map;
+    std::unordered_map<std::string, BindedCdevInfo> binded_cdev_info_map;
+};
+
+struct SensorInfo {
+    TemperatureType type;
+    ThrottlingArray hot_thresholds;
+    ThrottlingArray cold_thresholds;
+    ThrottlingArray hot_hysteresis;
+    ThrottlingArray cold_hysteresis;
+    std::string temp_path;
+    float vr_threshold;
+    float multiplier;
+    std::chrono::milliseconds polling_delay;
+    std::chrono::milliseconds passive_delay;
+    std::chrono::milliseconds time_resolution;
+    bool send_cb;
+    bool send_powerhint;
+    bool is_watch;
+    bool is_hidden;
+    std::unique_ptr<VirtualSensorInfo> virtual_sensor_info;
+    std::shared_ptr<ThrottlingInfo> throttling_info;
+};
+
+struct CdevInfo {
+    CoolingType type;
+    std::string read_path;
+    std::string write_path;
+    std::vector<float> state2power;
+    int max_state;
+};
+
+struct PowerRailInfo {
+    std::string rail;
+    int power_sample_count;
+    std::chrono::milliseconds power_sample_delay;
+    std::unique_ptr<VirtualPowerRailInfo> virtual_power_rail_info;
+};
+
+bool ParseThermalConfig(std::string_view config_path, Json::Value *config);
+bool ParseSensorInfo(const Json::Value &config,
+                     std::unordered_map<std::string, SensorInfo> *sensors_parsed);
+bool ParseCoolingDevice(const Json::Value &config,
+                        std::unordered_map<std::string, CdevInfo> *cooling_device_parsed);
+bool ParsePowerRailInfo(const Json::Value &config,
+                        std::unordered_map<std::string, PowerRailInfo> *power_rail_parsed);
+bool ParseStatsConfig(const Json::Value &config,
+                      const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+                      const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_,
+                      StatsConfig *stats_config);
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_stats_helper.cpp b/aidl/thermal/utils/thermal_stats_helper.cpp
new file mode 100644 (file)
index 0000000..daaaf1c
--- /dev/null
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "thermal_stats_helper.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+
+#include <algorithm>
+#include <numeric>
+#include <string_view>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+constexpr std::string_view kCustomThresholdSetSuffix("-TH-");
+constexpr std::string_view kCompressedThresholdSuffix("-CMBN-TH");
+
+using aidl::android::frameworks::stats::VendorAtom;
+namespace PixelAtoms = ::android::hardware::google::pixel::PixelAtoms;
+
+namespace {
+static std::shared_ptr<IStats> stats_client = nullptr;
+std::shared_ptr<IStats> getStatsService() {
+    static std::once_flag statsServiceFlag;
+    std::call_once(statsServiceFlag, []() {
+        const std::string instance = std::string() + IStats::descriptor + "/default";
+        bool isStatsDeclared = AServiceManager_isDeclared(instance.c_str());
+        if (!isStatsDeclared) {
+            LOG(ERROR) << "Stats service is not registered.";
+            return;
+        }
+        stats_client = IStats::fromBinder(
+                ndk::SpAIBinder(AServiceManager_waitForService(instance.c_str())));
+    });
+    return stats_client;
+}
+
+bool isRecordByDefaultThreshold(const std::variant<bool, std::unordered_set<std::string>>
+                                        &record_by_default_threshold_all_or_name_set_,
+                                std::string_view name) {
+    if (std::holds_alternative<bool>(record_by_default_threshold_all_or_name_set_)) {
+        return std::get<bool>(record_by_default_threshold_all_or_name_set_);
+    }
+    return std::get<std::unordered_set<std::string>>(record_by_default_threshold_all_or_name_set_)
+            .count(name.data());
+}
+
+template <typename T>
+int calculateThresholdBucket(const std::vector<T> &thresholds, T value) {
+    if (thresholds.empty()) {
+        LOG(VERBOSE) << "No threshold present, so bucket is " << value << " as int.";
+        return static_cast<int>(value);
+    }
+    auto threshold_idx = std::upper_bound(thresholds.begin(), thresholds.end(), value);
+    int bucket = (threshold_idx - thresholds.begin());
+    LOG(VERBOSE) << "For value: " << value << " bucket is: " << bucket;
+    return bucket;
+}
+
+}  // namespace
+
+bool ThermalStatsHelper::initializeStats(
+        const Json::Value &config,
+        const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_) {
+    StatsConfig stats_config;
+    if (!ParseStatsConfig(config, sensor_info_map_, cooling_device_info_map_, &stats_config)) {
+        LOG(ERROR) << "Failed to parse stats config";
+        return false;
+    }
+    bool is_initialized_ =
+            initializeSensorTempStats(stats_config.sensor_stats_info, sensor_info_map_) &&
+            initializeSensorCdevRequestStats(stats_config.cooling_device_request_info,
+                                             sensor_info_map_, cooling_device_info_map_);
+    if (is_initialized_) {
+        last_total_stats_report_time = boot_clock::now();
+        LOG(INFO) << "Thermal Stats Initialized Successfully";
+    }
+    return is_initialized_;
+}
+
+bool ThermalStatsHelper::initializeSensorCdevRequestStats(
+        const StatsInfo<int> &request_stats_info,
+        const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_cdev_request_stats_map_mutex_);
+    for (const auto &[sensor, sensor_info] : sensor_info_map_) {
+        for (const auto &binded_cdev_info_pair :
+             sensor_info.throttling_info->binded_cdev_info_map) {
+            const auto &cdev = binded_cdev_info_pair.first;
+            const auto &max_state =
+                    cooling_device_info_map_.at(binded_cdev_info_pair.first).max_state;
+            // Record by all state
+            if (isRecordByDefaultThreshold(
+                        request_stats_info.record_by_default_threshold_all_or_name_set_, cdev)) {
+                // if the number of states is greater / equal(as state starts from 0) than
+                // residency_buckets in atom combine the initial states
+                if (max_state >= kMaxStatsResidencyCount) {
+                    // buckets = [max_state -kMaxStatsResidencyCount + 1, ...max_state]
+                    //     idx = [1, .. max_state - (max_state - kMaxStatsResidencyCount + 1) + 1]
+                    //     idx = [1, .. kMaxStatsResidencyCount]
+                    const auto starting_state = max_state - kMaxStatsResidencyCount + 1;
+                    std::vector<int> thresholds(kMaxStatsResidencyCount);
+                    std::iota(thresholds.begin(), thresholds.end(), starting_state);
+                    const auto logging_name = cdev + kCompressedThresholdSuffix.data();
+                    ThresholdList<int> threshold_list(logging_name, thresholds);
+                    sensor_cdev_request_stats_map_[sensor][cdev]
+                            .stats_by_custom_threshold.emplace_back(threshold_list);
+                } else {
+                    // buckets = [0, 1, 2, 3, ...max_state]
+                    const auto default_threshold_time_in_state_size = max_state + 1;
+                    sensor_cdev_request_stats_map_[sensor][cdev].stats_by_default_threshold =
+                            StatsRecord(default_threshold_time_in_state_size);
+                }
+                LOG(INFO) << "Sensor Cdev user vote stats on basis of all state initialized for ["
+                          << sensor << "-" << cdev << "]";
+            }
+
+            // Record by custom threshold
+            if (request_stats_info.record_by_threshold.count(cdev)) {
+                for (const auto &threshold_list : request_stats_info.record_by_threshold.at(cdev)) {
+                    // check last threshold value(which is >= number of buckets as numbers in
+                    // threshold are strictly increasing from 0) is less than max_state
+                    if (threshold_list.thresholds.back() >= max_state) {
+                        LOG(ERROR) << "For sensor " << sensor << " bindedCdev: " << cdev
+                                   << "Invalid bindedCdev stats threshold: "
+                                   << threshold_list.thresholds.back() << " >= " << max_state;
+                        sensor_cdev_request_stats_map_.clear();
+                        return false;
+                    }
+                    sensor_cdev_request_stats_map_[sensor][cdev]
+                            .stats_by_custom_threshold.emplace_back(threshold_list);
+                    LOG(INFO)
+                            << "Sensor Cdev user vote stats on basis of threshold initialized for ["
+                            << sensor << "-" << cdev << "]";
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool ThermalStatsHelper::initializeSensorTempStats(
+        const StatsInfo<float> &sensor_stats_info,
+        const std::unordered_map<std::string, SensorInfo> &sensor_info_map_) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
+    const int severity_time_in_state_size = kThrottlingSeverityCount;
+    for (const auto &[sensor, sensor_info] : sensor_info_map_) {
+        // Record by severity
+        if (sensor_info.is_watch &&
+            isRecordByDefaultThreshold(
+                    sensor_stats_info.record_by_default_threshold_all_or_name_set_, sensor)) {
+            // number of buckets = number of severity
+            sensor_temp_stats_map_[sensor].stats_by_default_threshold =
+                    StatsRecord(severity_time_in_state_size);
+            LOG(INFO) << "Sensor temp stats on basis of severity initialized for [" << sensor
+                      << "]";
+        }
+
+        // Record by custom threshold
+        if (sensor_stats_info.record_by_threshold.count(sensor)) {
+            for (const auto &threshold_list : sensor_stats_info.record_by_threshold.at(sensor)) {
+                sensor_temp_stats_map_[sensor].stats_by_custom_threshold.emplace_back(
+                        threshold_list);
+                LOG(INFO) << "Sensor temp stats on basis of threshold initialized for [" << sensor
+                          << "]";
+            }
+        }
+    }
+    return true;
+}
+
+void ThermalStatsHelper::updateStatsRecord(StatsRecord *stats_record, int new_state) {
+    const auto now = boot_clock::now();
+    const auto cur_state_duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+            now - stats_record->cur_state_start_time);
+    LOG(VERBOSE) << "Adding duration " << cur_state_duration.count()
+                 << " for cur_state: " << stats_record->cur_state << " with value: "
+                 << stats_record->time_in_state_ms[stats_record->cur_state].count();
+    // Update last record end time
+    stats_record->time_in_state_ms[stats_record->cur_state] += cur_state_duration;
+    stats_record->cur_state_start_time = now;
+    stats_record->cur_state = new_state;
+}
+
+void ThermalStatsHelper::updateSensorCdevRequestStats(std::string_view sensor,
+                                                      std::string_view cdev, int new_value) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_cdev_request_stats_map_mutex_);
+    if (!sensor_cdev_request_stats_map_.count(sensor.data()) ||
+        !sensor_cdev_request_stats_map_[sensor.data()].count(cdev.data())) {
+        return;
+    }
+    auto &request_stats = sensor_cdev_request_stats_map_[sensor.data()][cdev.data()];
+    for (auto &stats_by_threshold : request_stats.stats_by_custom_threshold) {
+        int value = calculateThresholdBucket(stats_by_threshold.thresholds, new_value);
+        if (value != stats_by_threshold.stats_record.cur_state) {
+            LOG(VERBOSE) << "Updating bindedCdev stats for sensor: " << sensor.data()
+                         << " , cooling_device: " << cdev.data() << " with new value: " << value;
+            updateStatsRecord(&stats_by_threshold.stats_record, value);
+        }
+    }
+
+    if (request_stats.stats_by_default_threshold.has_value()) {
+        auto &stats_record = request_stats.stats_by_default_threshold.value();
+        if (new_value != stats_record.cur_state) {
+            LOG(VERBOSE) << "Updating bindedCdev stats for sensor: " << sensor.data()
+                         << " , cooling_device: " << cdev.data()
+                         << " with new value: " << new_value;
+            updateStatsRecord(&stats_record, new_value);
+        }
+    }
+}
+
+void ThermalStatsHelper::updateSensorTempStatsByThreshold(std::string_view sensor,
+                                                          float temperature) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
+    if (!sensor_temp_stats_map_.count(sensor.data())) {
+        return;
+    }
+    auto &sensor_temp_stats = sensor_temp_stats_map_[sensor.data()];
+    for (auto &stats_by_threshold : sensor_temp_stats.stats_by_custom_threshold) {
+        int value = calculateThresholdBucket(stats_by_threshold.thresholds, temperature);
+        if (value != stats_by_threshold.stats_record.cur_state) {
+            LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data()
+                         << " with value: " << value;
+            updateStatsRecord(&stats_by_threshold.stats_record, value);
+        }
+    }
+    if (temperature > sensor_temp_stats.max_temp) {
+        sensor_temp_stats.max_temp = temperature;
+        sensor_temp_stats.max_temp_timestamp = system_clock::now();
+    }
+    if (temperature < sensor_temp_stats.min_temp) {
+        sensor_temp_stats.min_temp = temperature;
+        sensor_temp_stats.min_temp_timestamp = system_clock::now();
+    }
+}
+
+void ThermalStatsHelper::updateSensorTempStatsBySeverity(std::string_view sensor,
+                                                         const ThrottlingSeverity &severity) {
+    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
+    if (sensor_temp_stats_map_.count(sensor.data()) &&
+        sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.has_value()) {
+        auto &stats_record =
+                sensor_temp_stats_map_[sensor.data()].stats_by_default_threshold.value();
+        int value = static_cast<int>(severity);
+        if (value != stats_record.cur_state) {
+            LOG(VERBOSE) << "Updating sensor stats for sensor: " << sensor.data()
+                         << " with value: " << value;
+            updateStatsRecord(&stats_record, value);
+        }
+    }
+}
+
+int ThermalStatsHelper::reportStats() {
+    const auto curTime = boot_clock::now();
+    const auto since_last_total_stats_update_ms =
+            std::chrono::duration_cast<std::chrono::milliseconds>(curTime -
+                                                                  last_total_stats_report_time);
+    LOG(VERBOSE) << "Duration from last total stats update is: "
+                 << since_last_total_stats_update_ms.count();
+    if (since_last_total_stats_update_ms < kUpdateIntervalMs) {
+        LOG(VERBOSE) << "Time elapsed since last update less than " << kUpdateIntervalMs.count();
+        return 0;
+    }
+
+    const std::shared_ptr<IStats> stats_client = getStatsService();
+    if (!stats_client) {
+        LOG(ERROR) << "Unable to get AIDL Stats service";
+        return -1;
+    }
+    int count_failed_reporting =
+            reportAllSensorTempStats(stats_client) + reportAllSensorCdevRequestStats(stats_client);
+    last_total_stats_report_time = curTime;
+    return count_failed_reporting;
+}
+
+int ThermalStatsHelper::reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client) {
+    int count_failed_reporting = 0;
+    std::unique_lock<std::shared_mutex> _lock(sensor_temp_stats_map_mutex_);
+    for (auto &[sensor, temp_stats] : sensor_temp_stats_map_) {
+        for (size_t threshold_set_idx = 0;
+             threshold_set_idx < temp_stats.stats_by_custom_threshold.size(); threshold_set_idx++) {
+            auto &stats_by_threshold = temp_stats.stats_by_custom_threshold[threshold_set_idx];
+            std::string sensor_name = stats_by_threshold.logging_name.value_or(
+                    sensor + kCustomThresholdSetSuffix.data() + std::to_string(threshold_set_idx));
+            if (!reportSensorTempStats(stats_client, sensor_name, temp_stats,
+                                       &stats_by_threshold.stats_record)) {
+                count_failed_reporting++;
+            }
+        }
+        if (temp_stats.stats_by_default_threshold.has_value()) {
+            if (!reportSensorTempStats(stats_client, sensor, temp_stats,
+                                       &temp_stats.stats_by_default_threshold.value())) {
+                count_failed_reporting++;
+            }
+        }
+    }
+    return count_failed_reporting;
+}
+
+bool ThermalStatsHelper::reportSensorTempStats(const std::shared_ptr<IStats> &stats_client,
+                                               std::string_view sensor,
+                                               const SensorTempStats &sensor_temp_stats,
+                                               StatsRecord *stats_record) {
+    LOG(VERBOSE) << "Reporting sensor stats for " << sensor;
+    // maintain a copy in case reporting fails
+    StatsRecord thermal_stats_before_reporting = *stats_record;
+    std::vector<VendorAtomValue> values(2);
+    values[0].set<VendorAtomValue::stringValue>(sensor);
+    std::vector<int64_t> time_in_state_ms = processStatsRecordForReporting(stats_record);
+    const auto since_last_update_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+            stats_record->cur_state_start_time - stats_record->last_stats_report_time);
+    values[1].set<VendorAtomValue::longValue>(since_last_update_ms.count());
+    VendorAtomValue tmp;
+    for (auto &time_in_state : time_in_state_ms) {
+        tmp.set<VendorAtomValue::longValue>(time_in_state);
+        values.push_back(tmp);
+    }
+    auto remaining_residency_buckets_count = kMaxStatsResidencyCount - time_in_state_ms.size();
+    if (remaining_residency_buckets_count > 0) {
+        tmp.set<VendorAtomValue::longValue>(0);
+        values.insert(values.end(), remaining_residency_buckets_count, tmp);
+    }
+    tmp.set<VendorAtomValue::floatValue>(sensor_temp_stats.max_temp);
+    values.push_back(tmp);
+    tmp.set<VendorAtomValue::longValue>(
+            system_clock::to_time_t(sensor_temp_stats.max_temp_timestamp));
+    values.push_back(tmp);
+    tmp.set<VendorAtomValue::floatValue>(sensor_temp_stats.min_temp);
+    values.push_back(tmp);
+    tmp.set<VendorAtomValue::longValue>(
+            system_clock::to_time_t(sensor_temp_stats.min_temp_timestamp));
+    values.push_back(tmp);
+
+    if (!reportAtom(stats_client, PixelAtoms::Atom::kVendorTempResidencyStats, std::move(values))) {
+        LOG(ERROR) << "Unable to report VendorTempResidencyStats to Stats service for "
+                      "sensor: "
+                   << sensor;
+        *stats_record = restoreStatsRecordOnFailure(std::move(thermal_stats_before_reporting));
+        return false;
+    }
+    // Update last time of stats reporting
+    stats_record->last_stats_report_time = boot_clock::now();
+    return true;
+}
+
+int ThermalStatsHelper::reportAllSensorCdevRequestStats(
+        const std::shared_ptr<IStats> &stats_client) {
+    int count_failed_reporting = 0;
+    std::unique_lock<std::shared_mutex> _lock(sensor_cdev_request_stats_map_mutex_);
+    for (auto &[sensor, cdev_request_stats_map] : sensor_cdev_request_stats_map_) {
+        for (auto &[cdev, request_stats] : cdev_request_stats_map) {
+            for (size_t threshold_set_idx = 0;
+                 threshold_set_idx < request_stats.stats_by_custom_threshold.size();
+                 threshold_set_idx++) {
+                auto &stats_by_threshold =
+                        request_stats.stats_by_custom_threshold[threshold_set_idx];
+                std::string cdev_name = stats_by_threshold.logging_name.value_or(
+                        cdev + kCustomThresholdSetSuffix.data() +
+                        std::to_string(threshold_set_idx));
+                if (!reportSensorCdevRequestStats(stats_client, sensor, cdev_name,
+                                                  &stats_by_threshold.stats_record)) {
+                    count_failed_reporting++;
+                }
+            }
+
+            if (request_stats.stats_by_default_threshold.has_value()) {
+                if (!reportSensorCdevRequestStats(
+                            stats_client, sensor, cdev,
+                            &request_stats.stats_by_default_threshold.value())) {
+                    count_failed_reporting++;
+                }
+            }
+        }
+    }
+    return count_failed_reporting;
+}
+
+bool ThermalStatsHelper::reportSensorCdevRequestStats(const std::shared_ptr<IStats> &stats_client,
+                                                      std::string_view sensor,
+                                                      std::string_view cdev,
+                                                      StatsRecord *stats_record) {
+    LOG(VERBOSE) << "Reporting bindedCdev stats for sensor: " << sensor
+                 << " cooling_device: " << cdev;
+    // maintain a copy in case reporting fails
+    StatsRecord thermal_stats_before_reporting = *stats_record;
+    std::vector<VendorAtomValue> values(3);
+    values[0].set<VendorAtomValue::stringValue>(sensor);
+    values[1].set<VendorAtomValue::stringValue>(cdev);
+    std::vector<int64_t> time_in_state_ms = processStatsRecordForReporting(stats_record);
+    const auto since_last_update_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
+            stats_record->cur_state_start_time - stats_record->last_stats_report_time);
+    values[2].set<VendorAtomValue::longValue>(since_last_update_ms.count());
+    VendorAtomValue tmp;
+    for (auto &time_in_state : time_in_state_ms) {
+        tmp.set<VendorAtomValue::longValue>(time_in_state);
+        values.push_back(tmp);
+    }
+
+    if (!reportAtom(stats_client, PixelAtoms::Atom::kVendorSensorCoolingDeviceStats,
+                    std::move(values))) {
+        LOG(ERROR) << "Unable to report VendorSensorCoolingDeviceStats to Stats "
+                      "service for sensor: "
+                   << sensor << " cooling_device: " << cdev;
+        *stats_record = restoreStatsRecordOnFailure(std::move(thermal_stats_before_reporting));
+        return false;
+    }
+    // Update last time of stats reporting
+    stats_record->last_stats_report_time = boot_clock::now();
+    return true;
+}
+
+std::vector<int64_t> ThermalStatsHelper::processStatsRecordForReporting(StatsRecord *stats_record) {
+    // update the last unclosed entry and start new record with same state
+    updateStatsRecord(stats_record, stats_record->cur_state);
+    std::vector<std::chrono::milliseconds> &time_in_state_ms = stats_record->time_in_state_ms;
+    // convert std::chrono::milliseconds time_in_state to int64_t vector for reporting
+    std::vector<int64_t> stats_residency(time_in_state_ms.size());
+    std::transform(time_in_state_ms.begin(), time_in_state_ms.end(), stats_residency.begin(),
+                   [](std::chrono::milliseconds time_ms) { return time_ms.count(); });
+    // clear previous stats
+    std::fill(time_in_state_ms.begin(), time_in_state_ms.end(), std::chrono::milliseconds::zero());
+    return stats_residency;
+}
+
+bool ThermalStatsHelper::reportAtom(const std::shared_ptr<IStats> &stats_client,
+                                    const int32_t &atom_id, std::vector<VendorAtomValue> &&values) {
+    LOG(VERBOSE) << "Reporting thermal stats for atom_id " << atom_id;
+    // Send vendor atom to IStats HAL
+    VendorAtom event = {.reverseDomainName = "", .atomId = atom_id, .values = std::move(values)};
+    const ndk::ScopedAStatus ret = stats_client->reportVendorAtom(event);
+    return ret.isOk();
+}
+
+StatsRecord ThermalStatsHelper::restoreStatsRecordOnFailure(
+        StatsRecord &&stats_record_before_failure) {
+    stats_record_before_failure.report_fail_count += 1;
+    // If consecutive count of failure is high, reset stat to avoid overflow
+    if (stats_record_before_failure.report_fail_count >= kMaxStatsReportingFailCount) {
+        return StatsRecord(stats_record_before_failure.time_in_state_ms.size(),
+                           stats_record_before_failure.cur_state);
+    } else {
+        return stats_record_before_failure;
+    }
+}
+
+std::unordered_map<std::string, SensorTempStats> ThermalStatsHelper::GetSensorTempStatsSnapshot() {
+    auto sensor_temp_stats_snapshot = sensor_temp_stats_map_;
+    for (auto &sensor_temp_stats_pair : sensor_temp_stats_snapshot) {
+        for (auto &temp_stats : sensor_temp_stats_pair.second.stats_by_custom_threshold) {
+            // update the last unclosed entry and start new record with same state
+            updateStatsRecord(&temp_stats.stats_record, temp_stats.stats_record.cur_state);
+        }
+        if (sensor_temp_stats_pair.second.stats_by_default_threshold.has_value()) {
+            auto &stats_by_default_threshold =
+                    sensor_temp_stats_pair.second.stats_by_default_threshold.value();
+            // update the last unclosed entry and start new record with same state
+            updateStatsRecord(&stats_by_default_threshold, stats_by_default_threshold.cur_state);
+        }
+    }
+    return sensor_temp_stats_snapshot;
+}
+
+std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
+ThermalStatsHelper::GetSensorCoolingDeviceRequestStatsSnapshot() {
+    auto sensor_cdev_request_stats_snapshot = sensor_cdev_request_stats_map_;
+    for (auto &sensor_cdev_request_stats_pair : sensor_cdev_request_stats_snapshot) {
+        for (auto &cdev_request_stats_pair : sensor_cdev_request_stats_pair.second) {
+            for (auto &request_stats : cdev_request_stats_pair.second.stats_by_custom_threshold) {
+                // update the last unclosed entry and start new record with same state
+                updateStatsRecord(&request_stats.stats_record,
+                                  request_stats.stats_record.cur_state);
+            }
+            if (cdev_request_stats_pair.second.stats_by_default_threshold.has_value()) {
+                auto &stats_by_default_threshold =
+                        cdev_request_stats_pair.second.stats_by_default_threshold.value();
+                // update the last unclosed entry and start new record with same state
+                updateStatsRecord(&stats_by_default_threshold,
+                                  stats_by_default_threshold.cur_state);
+            }
+        }
+    }
+    return sensor_cdev_request_stats_snapshot;
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_stats_helper.h b/aidl/thermal/utils/thermal_stats_helper.h
new file mode 100644 (file)
index 0000000..ef2f811
--- /dev/null
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/frameworks/stats/IStats.h>
+#include <aidl/android/hardware/thermal/Temperature.h>
+#include <android-base/chrono_utils.h>
+
+#include <chrono>
+#include <shared_mutex>
+#include <string_view>
+#include <unordered_map>
+#include <vector>
+
+#include "thermal_info.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using aidl::android::frameworks::stats::IStats;
+using aidl::android::frameworks::stats::VendorAtomValue;
+using ::android::base::boot_clock;
+using std::chrono::system_clock;
+using SystemTimePoint = std::chrono::time_point<std::chrono::system_clock>;
+
+constexpr int kMaxStatsReportingFailCount = 3;
+
+struct StatsRecord {
+    int cur_state; /* temperature / cdev state at current time */
+    boot_clock::time_point cur_state_start_time;
+    boot_clock::time_point last_stats_report_time = boot_clock::time_point::min();
+    std::vector<std::chrono::milliseconds> time_in_state_ms; /* stats array */
+    int report_fail_count = 0; /* Number of times failed to report stats */
+    explicit StatsRecord(const size_t &time_in_state_size, int state = 0)
+        : cur_state(state),
+          cur_state_start_time(boot_clock::now()),
+          last_stats_report_time(boot_clock::now()),
+          report_fail_count(0) {
+        time_in_state_ms = std::vector<std::chrono::milliseconds>(
+                time_in_state_size, std::chrono::milliseconds::zero());
+    }
+    StatsRecord() = default;
+    StatsRecord(const StatsRecord &) = default;
+    StatsRecord &operator=(const StatsRecord &) = default;
+    StatsRecord(StatsRecord &&) = default;
+    StatsRecord &operator=(StatsRecord &&) = default;
+    ~StatsRecord() = default;
+};
+
+template <typename ValueType>
+struct StatsByThreshold {
+    std::vector<ValueType> thresholds;
+    std::optional<std::string> logging_name;
+    StatsRecord stats_record;
+    explicit StatsByThreshold(ThresholdList<ValueType> threshold_list)
+        : thresholds(threshold_list.thresholds), logging_name(threshold_list.logging_name) {
+        // number of states = number of thresholds + 1
+        // e.g. threshold: [30, 50, 60]
+        //      buckets: [MIN - 30, 30 - 50, 50-60, 60-MAX]
+        int time_in_state_size = threshold_list.thresholds.size() + 1;
+        stats_record = StatsRecord(time_in_state_size);
+    }
+    StatsByThreshold() = default;
+    StatsByThreshold(const StatsByThreshold &) = default;
+    StatsByThreshold &operator=(const StatsByThreshold &) = default;
+    StatsByThreshold(StatsByThreshold &&) = default;
+    StatsByThreshold &operator=(StatsByThreshold &&) = default;
+    ~StatsByThreshold() = default;
+};
+
+template <typename ValueType>
+struct ThermalStats {
+    std::vector<StatsByThreshold<ValueType>> stats_by_custom_threshold;
+    std::optional<StatsRecord> stats_by_default_threshold;
+};
+
+struct SensorTempStats : ThermalStats<float> {
+    float max_temp = std::numeric_limits<float>::min();
+    SystemTimePoint max_temp_timestamp = SystemTimePoint::min();
+    float min_temp = std::numeric_limits<float>::max();
+    SystemTimePoint min_temp_timestamp = SystemTimePoint::min();
+};
+
+class ThermalStatsHelper {
+  public:
+    ThermalStatsHelper() = default;
+    ~ThermalStatsHelper() = default;
+    // Disallow copy and assign
+    ThermalStatsHelper(const ThermalStatsHelper &) = delete;
+    void operator=(const ThermalStatsHelper &) = delete;
+
+    bool initializeStats(const Json::Value &config,
+                         const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+                         const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_);
+    void updateSensorCdevRequestStats(std::string_view trigger_sensor, std::string_view cdev,
+                                      int new_state);
+    void updateSensorTempStatsBySeverity(std::string_view sensor,
+                                         const ThrottlingSeverity &severity);
+    void updateSensorTempStatsByThreshold(std::string_view sensor, float temperature);
+    /*
+     * Function to report all the stats by calling all specific stats reporting function.
+     * Returns:
+     *   0, if time_elapsed < kUpdateIntervalMs or if no failure in reporting
+     *  -1, if failed to get AIDL stats services
+     *  >0, count represents the number of stats failed to report.
+     */
+    int reportStats();
+    // Get a snapshot of Thermal Stats Sensor Map till that point in time
+    std::unordered_map<std::string, SensorTempStats> GetSensorTempStatsSnapshot();
+    // Get a snapshot of Thermal Stats Sensor Map till that point in time
+    std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
+    GetSensorCoolingDeviceRequestStatsSnapshot();
+
+  private:
+    static constexpr std::chrono::milliseconds kUpdateIntervalMs =
+            std::chrono::duration_cast<std::chrono::milliseconds>(24h);
+    boot_clock::time_point last_total_stats_report_time = boot_clock::time_point::min();
+
+    mutable std::shared_mutex sensor_temp_stats_map_mutex_;
+    // Temperature stats for each sensor being watched
+    std::unordered_map<std::string, SensorTempStats> sensor_temp_stats_map_;
+    mutable std::shared_mutex sensor_cdev_request_stats_map_mutex_;
+    // userVote request stat for the sensor to the corresponding cdev (sensor -> cdev ->
+    // StatsRecord)
+    std::unordered_map<std::string, std::unordered_map<std::string, ThermalStats<int>>>
+            sensor_cdev_request_stats_map_;
+
+    bool initializeSensorTempStats(
+            const StatsInfo<float> &sensor_stats_info,
+            const std::unordered_map<std::string, SensorInfo> &sensor_info_map_);
+    bool initializeSensorCdevRequestStats(
+            const StatsInfo<int> &request_stats_info,
+            const std::unordered_map<std::string, SensorInfo> &sensor_info_map_,
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map_);
+    void updateStatsRecord(StatsRecord *stats_record, int new_state);
+    int reportAllSensorTempStats(const std::shared_ptr<IStats> &stats_client);
+    bool reportSensorTempStats(const std::shared_ptr<IStats> &stats_client, std::string_view sensor,
+                               const SensorTempStats &sensor_temp_stats, StatsRecord *stats_record);
+    int reportAllSensorCdevRequestStats(const std::shared_ptr<IStats> &stats_client);
+    bool reportSensorCdevRequestStats(const std::shared_ptr<IStats> &stats_client,
+                                      std::string_view sensor, std::string_view cdev,
+                                      StatsRecord *stats_record);
+    bool reportAtom(const std::shared_ptr<IStats> &stats_client, const int32_t &atom_id,
+                    std::vector<VendorAtomValue> &&values);
+    std::vector<int64_t> processStatsRecordForReporting(StatsRecord *stats_record);
+    StatsRecord restoreStatsRecordOnFailure(StatsRecord &&stats_record_before_failure);
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_throttling.cpp b/aidl/thermal/utils/thermal_throttling.cpp
new file mode 100644 (file)
index 0000000..8aac95d
--- /dev/null
@@ -0,0 +1,767 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
+
+#include "thermal_throttling.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <utils/Trace.h>
+
+#include <iterator>
+#include <set>
+#include <sstream>
+#include <thread>
+#include <vector>
+
+#include "power_files.h"
+#include "thermal_info.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+using ::android::base::StringPrintf;
+
+// To find the next PID target state according to the current thermal severity
+size_t getTargetStateOfPID(const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity) {
+    size_t target_state = 0;
+
+    for (const auto &severity : ::ndk::enum_range<ThrottlingSeverity>()) {
+        size_t state = static_cast<size_t>(severity);
+        if (std::isnan(sensor_info.throttling_info->s_power[state])) {
+            continue;
+        }
+        target_state = state;
+        if (severity > curr_severity) {
+            break;
+        }
+    }
+    LOG(VERBOSE) << "PID target state = " << target_state;
+    return target_state;
+}
+
+void ThermalThrottling::clearThrottlingData(std::string_view sensor_name,
+                                            const SensorInfo &sensor_info) {
+    if (!thermal_throttling_status_map_.count(sensor_name.data())) {
+        return;
+    }
+    std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+
+    for (auto &pid_power_budget_pair :
+         thermal_throttling_status_map_.at(sensor_name.data()).pid_power_budget_map) {
+        pid_power_budget_pair.second = std::numeric_limits<int>::max();
+    }
+
+    for (auto &pid_cdev_request_pair :
+         thermal_throttling_status_map_.at(sensor_name.data()).pid_cdev_request_map) {
+        pid_cdev_request_pair.second = 0;
+    }
+
+    for (auto &hardlimit_cdev_request_pair :
+         thermal_throttling_status_map_.at(sensor_name.data()).hardlimit_cdev_request_map) {
+        hardlimit_cdev_request_pair.second = 0;
+    }
+
+    for (auto &throttling_release_pair :
+         thermal_throttling_status_map_.at(sensor_name.data()).throttling_release_map) {
+        throttling_release_pair.second = 0;
+    }
+
+    thermal_throttling_status_map_[sensor_name.data()].prev_err = NAN;
+    thermal_throttling_status_map_[sensor_name.data()].i_budget =
+            sensor_info.throttling_info->i_default;
+    thermal_throttling_status_map_[sensor_name.data()].prev_target =
+            static_cast<size_t>(ThrottlingSeverity::NONE);
+    thermal_throttling_status_map_[sensor_name.data()].prev_power_budget = NAN;
+    thermal_throttling_status_map_[sensor_name.data()].tran_cycle = 0;
+
+    return;
+}
+
+bool ThermalThrottling::registerThermalThrottling(
+        std::string_view sensor_name, const std::shared_ptr<ThrottlingInfo> &throttling_info,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
+    if (thermal_throttling_status_map_.count(sensor_name.data())) {
+        LOG(ERROR) << "Sensor " << sensor_name.data() << " throttling map has been registered";
+        return false;
+    }
+
+    if (throttling_info == nullptr) {
+        LOG(ERROR) << "Sensor " << sensor_name.data() << " has no throttling info";
+        return false;
+    }
+
+    thermal_throttling_status_map_[sensor_name.data()].prev_err = NAN;
+    thermal_throttling_status_map_[sensor_name.data()].i_budget = throttling_info->i_default;
+    thermal_throttling_status_map_[sensor_name.data()].prev_target =
+            static_cast<size_t>(ThrottlingSeverity::NONE);
+    thermal_throttling_status_map_[sensor_name.data()].prev_power_budget = NAN;
+    thermal_throttling_status_map_[sensor_name.data()].tran_cycle = 0;
+
+    for (auto &binded_cdev_pair : throttling_info->binded_cdev_info_map) {
+        if (!cooling_device_info_map.count(binded_cdev_pair.first)) {
+            LOG(ERROR) << "Could not find " << sensor_name.data() << "'s binded CDEV "
+                       << binded_cdev_pair.first;
+            return false;
+        }
+        // Register PID throttling map
+        for (const auto &cdev_weight : binded_cdev_pair.second.cdev_weight_for_pid) {
+            if (!std::isnan(cdev_weight)) {
+                thermal_throttling_status_map_[sensor_name.data()]
+                        .pid_power_budget_map[binded_cdev_pair.first] =
+                        std::numeric_limits<int>::max();
+                thermal_throttling_status_map_[sensor_name.data()]
+                        .pid_cdev_request_map[binded_cdev_pair.first] = 0;
+                thermal_throttling_status_map_[sensor_name.data()]
+                        .cdev_status_map[binded_cdev_pair.first] = 0;
+                cdev_all_request_map_[binded_cdev_pair.first].insert(0);
+                break;
+            }
+        }
+        // Register hard limit throttling map
+        for (const auto &limit_info : binded_cdev_pair.second.limit_info) {
+            if (limit_info > 0) {
+                thermal_throttling_status_map_[sensor_name.data()]
+                        .hardlimit_cdev_request_map[binded_cdev_pair.first] = 0;
+                thermal_throttling_status_map_[sensor_name.data()]
+                        .cdev_status_map[binded_cdev_pair.first] = 0;
+                cdev_all_request_map_[binded_cdev_pair.first].insert(0);
+                break;
+            }
+        }
+        // Register throttling release map if power threshold exists
+        if (!binded_cdev_pair.second.power_rail.empty()) {
+            for (const auto &power_threshold : binded_cdev_pair.second.power_thresholds) {
+                if (!std::isnan(power_threshold)) {
+                    thermal_throttling_status_map_[sensor_name.data()]
+                            .throttling_release_map[binded_cdev_pair.first] = 0;
+                    break;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+// return power budget based on PID algo
+float ThermalThrottling::updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info,
+                                           std::chrono::milliseconds time_elapsed_ms,
+                                           ThrottlingSeverity curr_severity) {
+    float p = 0, d = 0;
+    float power_budget = std::numeric_limits<float>::max();
+    bool target_changed = false;
+    float budget_transient = 0.0;
+    auto &throttling_status = thermal_throttling_status_map_.at(temp.name);
+    std::string sensor_name = temp.name;
+
+    if (curr_severity == ThrottlingSeverity::NONE) {
+        return power_budget;
+    }
+
+    const auto target_state = getTargetStateOfPID(sensor_info, curr_severity);
+    if (throttling_status.prev_target != static_cast<size_t>(ThrottlingSeverity::NONE) &&
+        target_state != throttling_status.prev_target &&
+        sensor_info.throttling_info->tran_cycle > 0) {
+        throttling_status.tran_cycle = sensor_info.throttling_info->tran_cycle - 1;
+        target_changed = true;
+    }
+    throttling_status.prev_target = target_state;
+
+    // Compute PID
+    float err = sensor_info.hot_thresholds[target_state] - temp.value;
+    p = err * (err < 0 ? sensor_info.throttling_info->k_po[target_state]
+                       : sensor_info.throttling_info->k_pu[target_state]);
+
+    if (err < sensor_info.throttling_info->i_cutoff[target_state]) {
+        throttling_status.i_budget += err * sensor_info.throttling_info->k_i[target_state];
+    }
+
+    if (fabsf(throttling_status.i_budget) > sensor_info.throttling_info->i_max[target_state]) {
+        throttling_status.i_budget = sensor_info.throttling_info->i_max[target_state] *
+                                     (throttling_status.i_budget > 0 ? 1 : -1);
+    }
+
+    if (!std::isnan(throttling_status.prev_err) &&
+        time_elapsed_ms != std::chrono::milliseconds::zero()) {
+        d = sensor_info.throttling_info->k_d[target_state] * (err - throttling_status.prev_err) /
+            time_elapsed_ms.count();
+    }
+
+    throttling_status.prev_err = err;
+    // Calculate power budget
+    power_budget =
+            sensor_info.throttling_info->s_power[target_state] + p + throttling_status.i_budget + d;
+    if (power_budget < sensor_info.throttling_info->min_alloc_power[target_state]) {
+        power_budget = sensor_info.throttling_info->min_alloc_power[target_state];
+    }
+    if (power_budget > sensor_info.throttling_info->max_alloc_power[target_state]) {
+        power_budget = sensor_info.throttling_info->max_alloc_power[target_state];
+    }
+
+    if (target_changed) {
+        throttling_status.budget_transient = throttling_status.prev_power_budget - power_budget;
+    }
+
+    if (throttling_status.tran_cycle) {
+        budget_transient = throttling_status.budget_transient *
+                           ((static_cast<float>(throttling_status.tran_cycle) /
+                             static_cast<float>(sensor_info.throttling_info->tran_cycle)));
+        power_budget += budget_transient;
+        throttling_status.tran_cycle--;
+    }
+
+    LOG(INFO) << temp.name << " power_budget=" << power_budget << " err=" << err
+              << " s_power=" << sensor_info.throttling_info->s_power[target_state]
+              << " time_elapsed_ms=" << time_elapsed_ms.count() << " p=" << p
+              << " i=" << throttling_status.i_budget << " d=" << d
+              << " budget transient=" << budget_transient << " control target=" << target_state;
+
+    ATRACE_INT((sensor_name + std::string("-power_budget")).c_str(),
+               static_cast<int>(power_budget));
+    ATRACE_INT((sensor_name + std::string("-s_power")).c_str(),
+               static_cast<int>(sensor_info.throttling_info->s_power[target_state]));
+    ATRACE_INT((sensor_name + std::string("-time_elapsed_ms")).c_str(),
+               static_cast<int>(time_elapsed_ms.count()));
+    ATRACE_INT((sensor_name + std::string("-budget_transient")).c_str(),
+               static_cast<int>(budget_transient));
+    ATRACE_INT((sensor_name + std::string("-i")).c_str(),
+               static_cast<int>(throttling_status.i_budget));
+    ATRACE_INT((sensor_name + std::string("-target_state")).c_str(),
+               static_cast<int>(target_state));
+
+    ATRACE_INT((sensor_name + std::string("-err")).c_str(), static_cast<int>(err / sensor_info.multiplier));
+    ATRACE_INT((sensor_name + std::string("-p")).c_str(), static_cast<int>(p));
+    ATRACE_INT((sensor_name + std::string("-d")).c_str(), static_cast<int>(d));
+    ATRACE_INT((sensor_name + std::string("-temp")).c_str(), static_cast<int>(temp.value / sensor_info.multiplier));
+
+    throttling_status.prev_power_budget = power_budget;
+
+    return power_budget;
+}
+
+float ThermalThrottling::computeExcludedPower(
+        const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity,
+        const std::unordered_map<std::string, PowerStatus> &power_status_map, std::string *log_buf,
+        std::string_view sensor_name) {
+    float excluded_power = 0.0;
+
+    for (const auto &excluded_power_info_pair :
+         sensor_info.throttling_info->excluded_power_info_map) {
+        const auto last_updated_avg_power =
+                power_status_map.at(excluded_power_info_pair.first).last_updated_avg_power;
+        if (!std::isnan(last_updated_avg_power)) {
+            excluded_power += last_updated_avg_power *
+                              excluded_power_info_pair.second[static_cast<size_t>(curr_severity)];
+            log_buf->append(StringPrintf(
+                    "(%s: %0.2f mW, cdev_weight: %f)", excluded_power_info_pair.first.c_str(),
+                    last_updated_avg_power,
+                    excluded_power_info_pair.second[static_cast<size_t>(curr_severity)]));
+
+            ATRACE_INT((std::string(sensor_name) + std::string("-") +
+                        excluded_power_info_pair.first + std::string("-avg_power"))
+                               .c_str(),
+                       static_cast<int>(last_updated_avg_power));
+        }
+    }
+
+    ATRACE_INT((std::string(sensor_name) + std::string("-excluded_power")).c_str(),
+               static_cast<int>(excluded_power));
+    return excluded_power;
+}
+
+// Allocate power budget to binded cooling devices base on the real ODPM power data
+bool ThermalThrottling::allocatePowerToCdev(
+        const Temperature &temp, const SensorInfo &sensor_info,
+        const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
+        const std::unordered_map<std::string, PowerStatus> &power_status_map,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
+    float total_weight = 0;
+    float last_updated_avg_power = NAN;
+    float allocated_power = 0;
+    float allocated_weight = 0;
+    bool low_power_device_check = true;
+    bool is_budget_allocated = false;
+    bool power_data_invalid = false;
+    std::set<std::string> allocated_cdev;
+    std::string log_buf;
+
+    std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+    auto total_power_budget = updatePowerBudget(temp, sensor_info, time_elapsed_ms, curr_severity);
+
+    if (sensor_info.throttling_info->excluded_power_info_map.size()) {
+        total_power_budget -= computeExcludedPower(sensor_info, curr_severity, power_status_map,
+                                                   &log_buf, temp.name);
+        total_power_budget = std::max(total_power_budget, 0.0f);
+        if (!log_buf.empty()) {
+            LOG(INFO) << temp.name << " power budget=" << total_power_budget << " after " << log_buf
+                      << " is excluded";
+        }
+    }
+
+    // Compute total cdev weight
+    for (const auto &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) {
+        const auto cdev_weight = binded_cdev_info_pair.second
+                                         .cdev_weight_for_pid[static_cast<size_t>(curr_severity)];
+        if (std::isnan(cdev_weight) || cdev_weight == 0) {
+            allocated_cdev.insert(binded_cdev_info_pair.first);
+            continue;
+        }
+        total_weight += cdev_weight;
+    }
+
+    while (!is_budget_allocated) {
+        for (const auto &binded_cdev_info_pair :
+             sensor_info.throttling_info->binded_cdev_info_map) {
+            float cdev_power_adjustment = 0;
+            const auto cdev_weight =
+                    binded_cdev_info_pair.second
+                            .cdev_weight_for_pid[static_cast<size_t>(curr_severity)];
+
+            if (allocated_cdev.count(binded_cdev_info_pair.first)) {
+                continue;
+            }
+            if (std::isnan(cdev_weight) || !cdev_weight) {
+                allocated_cdev.insert(binded_cdev_info_pair.first);
+                continue;
+            }
+
+            // Get the power data
+            if (!power_data_invalid) {
+                if (!binded_cdev_info_pair.second.power_rail.empty()) {
+                    last_updated_avg_power =
+                            power_status_map.at(binded_cdev_info_pair.second.power_rail)
+                                    .last_updated_avg_power;
+                    if (std::isnan(last_updated_avg_power)) {
+                        LOG(VERBOSE) << "power data is under collecting";
+                        power_data_invalid = true;
+                        break;
+                    }
+
+                    ATRACE_INT((temp.name + std::string("-") +
+                                binded_cdev_info_pair.second.power_rail + std::string("-avg_power"))
+                                       .c_str(),
+                               static_cast<int>(last_updated_avg_power));
+                } else {
+                    power_data_invalid = true;
+                    break;
+                }
+                if (binded_cdev_info_pair.second.throttling_with_power_link) {
+                    return false;
+                }
+            }
+
+            auto cdev_power_budget = total_power_budget * (cdev_weight / total_weight);
+            cdev_power_adjustment = cdev_power_budget - last_updated_avg_power;
+
+            if (low_power_device_check) {
+                // Share the budget for the CDEV which power is lower than target
+                if (cdev_power_adjustment > 0 &&
+                    thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
+                            binded_cdev_info_pair.first) == 0) {
+                    allocated_power += last_updated_avg_power;
+                    allocated_weight += cdev_weight;
+                    allocated_cdev.insert(binded_cdev_info_pair.first);
+                    if (!binded_cdev_info_pair.second.power_rail.empty()) {
+                        log_buf.append(StringPrintf("(%s: %0.2f mW)",
+                                                    binded_cdev_info_pair.second.power_rail.c_str(),
+                                                    last_updated_avg_power));
+                    }
+                    LOG(VERBOSE) << temp.name << " binded " << binded_cdev_info_pair.first
+                                 << " has been already at min state 0";
+                }
+            } else {
+                const CdevInfo &cdev_info = cooling_device_info_map.at(binded_cdev_info_pair.first);
+                if (!binded_cdev_info_pair.second.power_rail.empty()) {
+                    log_buf.append(StringPrintf("(%s: %0.2f mW)",
+                                                binded_cdev_info_pair.second.power_rail.c_str(),
+                                                last_updated_avg_power));
+                }
+                // Ignore the power distribution if the CDEV has no space to reduce power
+                if ((cdev_power_adjustment < 0 &&
+                     thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
+                             binded_cdev_info_pair.first) == cdev_info.max_state)) {
+                    LOG(VERBOSE) << temp.name << " binded " << binded_cdev_info_pair.first
+                                 << " has been already at max state " << cdev_info.max_state;
+                    continue;
+                }
+
+                if (!power_data_invalid && binded_cdev_info_pair.second.power_rail != "") {
+                    auto cdev_curr_power_budget =
+                            thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
+                                    binded_cdev_info_pair.first);
+
+                    if (last_updated_avg_power > cdev_curr_power_budget) {
+                        cdev_power_budget = cdev_curr_power_budget +=
+                                (cdev_power_adjustment *
+                                 (cdev_curr_power_budget / last_updated_avg_power));
+                    } else {
+                        cdev_power_budget = cdev_curr_power_budget += cdev_power_adjustment;
+                    }
+                } else {
+                    cdev_power_budget = total_power_budget * (cdev_weight / total_weight);
+                }
+
+                if (!std::isnan(cdev_info.state2power[0]) &&
+                    cdev_power_budget > cdev_info.state2power[0]) {
+                    cdev_power_budget = cdev_info.state2power[0];
+                } else if (cdev_power_budget < 0) {
+                    cdev_power_budget = 0;
+                }
+
+                int max_cdev_vote;
+                if (!getCdevMaxRequest(binded_cdev_info_pair.first, &max_cdev_vote)) {
+                    return false;
+                }
+
+                const auto curr_cdev_vote =
+                        thermal_throttling_status_map_[temp.name].pid_cdev_request_map.at(
+                                binded_cdev_info_pair.first);
+
+                if (binded_cdev_info_pair.second.max_release_step !=
+                            std::numeric_limits<int>::max() &&
+                    (power_data_invalid || cdev_power_adjustment > 0)) {
+                    if (!power_data_invalid && curr_cdev_vote < max_cdev_vote) {
+                        cdev_power_budget = cdev_info.state2power[curr_cdev_vote];
+                        LOG(VERBOSE) << temp.name << "'s " << binded_cdev_info_pair.first
+                                     << " vote: " << curr_cdev_vote
+                                     << " is lower than max cdev vote: " << max_cdev_vote;
+                    } else {
+                        const auto target_state = std::max(
+                                curr_cdev_vote - binded_cdev_info_pair.second.max_release_step, 0);
+                        cdev_power_budget =
+                                std::min(cdev_power_budget, cdev_info.state2power[target_state]);
+                    }
+                }
+
+                if (binded_cdev_info_pair.second.max_throttle_step !=
+                            std::numeric_limits<int>::max() &&
+                    (power_data_invalid || cdev_power_adjustment < 0)) {
+                    const auto target_state = std::min(
+                            curr_cdev_vote + binded_cdev_info_pair.second.max_throttle_step,
+                            cdev_info.max_state);
+                    cdev_power_budget =
+                            std::max(cdev_power_budget, cdev_info.state2power[target_state]);
+                }
+
+                thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
+                        binded_cdev_info_pair.first) = cdev_power_budget;
+                LOG(VERBOSE) << temp.name << " allocate "
+                             << thermal_throttling_status_map_[temp.name].pid_power_budget_map.at(
+                                        binded_cdev_info_pair.first)
+                             << "mW to " << binded_cdev_info_pair.first
+                             << "(cdev_weight=" << cdev_weight << ")";
+            }
+        }
+
+        if (!power_data_invalid) {
+            total_power_budget -= allocated_power;
+            total_weight -= allocated_weight;
+        }
+        allocated_power = 0;
+        allocated_weight = 0;
+
+        if (low_power_device_check) {
+            low_power_device_check = false;
+        } else {
+            is_budget_allocated = true;
+        }
+    }
+    if (log_buf.size()) {
+        LOG(INFO) << temp.name << " binded power rails: " << log_buf;
+    }
+    return true;
+}
+
+void ThermalThrottling::updateCdevRequestByPower(
+        std::string sensor_name,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
+    size_t i;
+
+    std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+    for (auto &pid_power_budget_pair :
+         thermal_throttling_status_map_[sensor_name.data()].pid_power_budget_map) {
+        const CdevInfo &cdev_info = cooling_device_info_map.at(pid_power_budget_pair.first);
+
+        for (i = 0; i < cdev_info.state2power.size() - 1; ++i) {
+            if (pid_power_budget_pair.second >= cdev_info.state2power[i]) {
+                break;
+            }
+        }
+        thermal_throttling_status_map_[sensor_name.data()].pid_cdev_request_map.at(
+                pid_power_budget_pair.first) = static_cast<int>(i);
+    }
+
+    return;
+}
+
+void ThermalThrottling::updateCdevRequestBySeverity(std::string_view sensor_name,
+                                                    const SensorInfo &sensor_info,
+                                                    ThrottlingSeverity curr_severity) {
+    std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+    for (auto const &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) {
+        thermal_throttling_status_map_[sensor_name.data()].hardlimit_cdev_request_map.at(
+                binded_cdev_info_pair.first) =
+                binded_cdev_info_pair.second.limit_info[static_cast<size_t>(curr_severity)];
+        LOG(VERBOSE) << "Hard Limit: Sensor " << sensor_name.data() << " update cdev "
+                     << binded_cdev_info_pair.first << " to "
+                     << thermal_throttling_status_map_[sensor_name.data()]
+                                .hardlimit_cdev_request_map.at(binded_cdev_info_pair.first);
+    }
+}
+
+bool ThermalThrottling::throttlingReleaseUpdate(
+        std::string_view sensor_name,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
+        const std::unordered_map<std::string, PowerStatus> &power_status_map,
+        const ThrottlingSeverity severity, const SensorInfo &sensor_info) {
+    ATRACE_CALL();
+    std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+    if (!thermal_throttling_status_map_.count(sensor_name.data())) {
+        return false;
+    }
+    auto &thermal_throttling_status = thermal_throttling_status_map_.at(sensor_name.data());
+    for (const auto &binded_cdev_info_pair : sensor_info.throttling_info->binded_cdev_info_map) {
+        float avg_power = -1;
+
+        if (!thermal_throttling_status.throttling_release_map.count(binded_cdev_info_pair.first) ||
+            !power_status_map.count(binded_cdev_info_pair.second.power_rail)) {
+            return false;
+        }
+
+        const auto max_state = cooling_device_info_map.at(binded_cdev_info_pair.first).max_state;
+
+        auto &release_step =
+                thermal_throttling_status.throttling_release_map.at(binded_cdev_info_pair.first);
+        avg_power =
+                power_status_map.at(binded_cdev_info_pair.second.power_rail).last_updated_avg_power;
+
+        if (std::isnan(avg_power) || avg_power < 0) {
+            release_step = binded_cdev_info_pair.second.throttling_with_power_link ? max_state : 0;
+            continue;
+        }
+
+        bool is_over_budget = true;
+        if (!binded_cdev_info_pair.second.high_power_check) {
+            if (avg_power <
+                binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]) {
+                is_over_budget = false;
+            }
+        } else {
+            if (avg_power >
+                binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]) {
+                is_over_budget = false;
+            }
+        }
+        LOG(INFO) << sensor_name.data() << "'s " << binded_cdev_info_pair.first
+                  << " binded power rail " << binded_cdev_info_pair.second.power_rail
+                  << ": power threshold = "
+                  << binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]
+                  << ", avg power = " << avg_power;
+        std::string atrace_prefix = ::android::base::StringPrintf(
+                "%s-%s", sensor_name.data(), binded_cdev_info_pair.second.power_rail.data());
+        ATRACE_INT(
+                (atrace_prefix + std::string("-power_threshold")).c_str(),
+                static_cast<int>(
+                        binded_cdev_info_pair.second.power_thresholds[static_cast<int>(severity)]));
+        ATRACE_INT((atrace_prefix + std::string("-avg_power")).c_str(), avg_power);
+
+        switch (binded_cdev_info_pair.second.release_logic) {
+            case ReleaseLogic::INCREASE:
+                if (!is_over_budget) {
+                    if (std::abs(release_step) < static_cast<int>(max_state)) {
+                        release_step--;
+                    }
+                } else {
+                    release_step = 0;
+                }
+                break;
+            case ReleaseLogic::DECREASE:
+                if (!is_over_budget) {
+                    if (release_step < static_cast<int>(max_state)) {
+                        release_step++;
+                    }
+                } else {
+                    release_step = 0;
+                }
+                break;
+            case ReleaseLogic::STEPWISE:
+                if (!is_over_budget) {
+                    if (release_step < static_cast<int>(max_state)) {
+                        release_step++;
+                    }
+                } else {
+                    if (std::abs(release_step) < static_cast<int>(max_state)) {
+                        release_step--;
+                    }
+                }
+                break;
+            case ReleaseLogic::RELEASE_TO_FLOOR:
+                release_step = is_over_budget ? 0 : max_state;
+                break;
+            case ReleaseLogic::NONE:
+            default:
+                break;
+        }
+    }
+    return true;
+}
+
+void ThermalThrottling::thermalThrottlingUpdate(
+        const Temperature &temp, const SensorInfo &sensor_info,
+        const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
+        const std::unordered_map<std::string, PowerStatus> &power_status_map,
+        const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map) {
+    if (!thermal_throttling_status_map_.count(temp.name)) {
+        return;
+    }
+
+    if (thermal_throttling_status_map_[temp.name].pid_power_budget_map.size()) {
+        if (!allocatePowerToCdev(temp, sensor_info, curr_severity, time_elapsed_ms,
+                                 power_status_map, cooling_device_info_map)) {
+            LOG(ERROR) << "Sensor " << temp.name << " PID request cdev failed";
+            // Clear the CDEV request if the power budget is failed to be allocated
+            for (auto &pid_cdev_request_pair :
+                 thermal_throttling_status_map_[temp.name].pid_cdev_request_map) {
+                pid_cdev_request_pair.second = 0;
+            }
+        }
+        updateCdevRequestByPower(temp.name, cooling_device_info_map);
+    }
+
+    if (thermal_throttling_status_map_[temp.name].hardlimit_cdev_request_map.size()) {
+        updateCdevRequestBySeverity(temp.name.c_str(), sensor_info, curr_severity);
+    }
+
+    if (thermal_throttling_status_map_[temp.name].throttling_release_map.size()) {
+        throttlingReleaseUpdate(temp.name.c_str(), cooling_device_info_map, power_status_map,
+                                curr_severity, sensor_info);
+    }
+}
+
+void ThermalThrottling::computeCoolingDevicesRequest(
+        std::string_view sensor_name, const SensorInfo &sensor_info,
+        const ThrottlingSeverity curr_severity, std::vector<std::string> *cooling_devices_to_update,
+        ThermalStatsHelper *thermal_stats_helper) {
+    int release_step = 0;
+    std::unique_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+
+    if (!thermal_throttling_status_map_.count(sensor_name.data())) {
+        return;
+    }
+
+    auto &thermal_throttling_status = thermal_throttling_status_map_.at(sensor_name.data());
+    const auto &cdev_release_map = thermal_throttling_status.throttling_release_map;
+
+    for (auto &cdev_request_pair : thermal_throttling_status.cdev_status_map) {
+        int pid_cdev_request = 0;
+        int hardlimit_cdev_request = 0;
+        const auto &cdev_name = cdev_request_pair.first;
+        const auto &binded_cdev_info =
+                sensor_info.throttling_info->binded_cdev_info_map.at(cdev_name);
+        const auto cdev_ceiling = binded_cdev_info.cdev_ceiling[static_cast<size_t>(curr_severity)];
+        const auto cdev_floor =
+                binded_cdev_info.cdev_floor_with_power_link[static_cast<size_t>(curr_severity)];
+        release_step = 0;
+
+        if (thermal_throttling_status.pid_cdev_request_map.count(cdev_name)) {
+            pid_cdev_request = thermal_throttling_status.pid_cdev_request_map.at(cdev_name);
+        }
+
+        if (thermal_throttling_status.hardlimit_cdev_request_map.count(cdev_name)) {
+            hardlimit_cdev_request =
+                    thermal_throttling_status.hardlimit_cdev_request_map.at(cdev_name);
+        }
+
+        if (cdev_release_map.count(cdev_name)) {
+            release_step = cdev_release_map.at(cdev_name);
+        }
+
+        LOG(VERBOSE) << sensor_name.data() << " binded cooling device " << cdev_name
+                     << "'s pid_request=" << pid_cdev_request
+                     << " hardlimit_cdev_request=" << hardlimit_cdev_request
+                     << " release_step=" << release_step
+                     << " cdev_floor_with_power_link=" << cdev_floor
+                     << " cdev_ceiling=" << cdev_ceiling;
+        std::string atrace_prefix =
+                ::android::base::StringPrintf("%s-%s", sensor_name.data(), cdev_name.data());
+        ATRACE_INT((atrace_prefix + std::string("-pid_request")).c_str(), pid_cdev_request);
+        ATRACE_INT((atrace_prefix + std::string("-hardlimit_request")).c_str(),
+                   hardlimit_cdev_request);
+        ATRACE_INT((atrace_prefix + std::string("-release_step")).c_str(), release_step);
+        ATRACE_INT((atrace_prefix + std::string("-cdev_floor")).c_str(), cdev_floor);
+        ATRACE_INT((atrace_prefix + std::string("-cdev_ceiling")).c_str(), cdev_ceiling);
+
+        auto request_state = std::max(pid_cdev_request, hardlimit_cdev_request);
+        if (release_step) {
+            if (release_step >= request_state) {
+                request_state = 0;
+            } else {
+                request_state = request_state - release_step;
+            }
+            // Only check the cdev_floor when release step is non zero
+            request_state = std::max(request_state, cdev_floor);
+        }
+        request_state = std::min(request_state, cdev_ceiling);
+        if (cdev_request_pair.second != request_state) {
+            if (updateCdevMaxRequestAndNotifyIfChange(cdev_name, cdev_request_pair.second,
+                                                      request_state)) {
+                cooling_devices_to_update->emplace_back(cdev_name);
+            }
+            cdev_request_pair.second = request_state;
+            // Update sensor cdev request time in state
+            thermal_stats_helper->updateSensorCdevRequestStats(sensor_name, cdev_name,
+                                                               cdev_request_pair.second);
+        }
+    }
+}
+
+bool ThermalThrottling::updateCdevMaxRequestAndNotifyIfChange(std::string_view cdev_name,
+                                                              int cur_request, int new_request) {
+    std::unique_lock<std::shared_mutex> _lock(cdev_all_request_map_mutex_);
+    auto &request_set = cdev_all_request_map_.at(cdev_name.data());
+    int cur_max_request = (*request_set.begin());
+    // Remove old cdev request and add the new one.
+    request_set.erase(request_set.find(cur_request));
+    request_set.insert(new_request);
+    // Check if there is any change in aggregated max cdev request.
+    int new_max_request = (*request_set.begin());
+    LOG(VERBOSE) << "For cooling device [" << cdev_name.data()
+                 << "] cur_max_request is: " << cur_max_request
+                 << " new_max_request is: " << new_max_request;
+    return new_max_request != cur_max_request;
+}
+
+bool ThermalThrottling::getCdevMaxRequest(std::string_view cdev_name, int *max_state) {
+    std::shared_lock<std::shared_mutex> _lock(cdev_all_request_map_mutex_);
+    if (!cdev_all_request_map_.count(cdev_name.data())) {
+        LOG(ERROR) << "Cooling device [" << cdev_name.data()
+                   << "] not present in cooling device request map";
+        return false;
+    }
+    *max_state = *cdev_all_request_map_.at(cdev_name.data()).begin();
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_throttling.h b/aidl/thermal/utils/thermal_throttling.h
new file mode 100644 (file)
index 0000000..e83ee06
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/thermal/Temperature.h>
+
+#include <queue>
+#include <set>
+#include <shared_mutex>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "power_files.h"
+#include "thermal_info.h"
+#include "thermal_stats_helper.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+struct ThermalThrottlingStatus {
+    std::unordered_map<std::string, int> pid_power_budget_map;
+    std::unordered_map<std::string, int> pid_cdev_request_map;
+    std::unordered_map<std::string, int> hardlimit_cdev_request_map;
+    std::unordered_map<std::string, int> throttling_release_map;
+    std::unordered_map<std::string, int> cdev_status_map;
+    float prev_err;
+    float i_budget;
+    float prev_target;
+    float prev_power_budget;
+    float budget_transient;
+    int tran_cycle;
+};
+
+// Return the control temp target of PID algorithm
+size_t getTargetStateOfPID(const SensorInfo &sensor_info, const ThrottlingSeverity curr_severity);
+
+// A helper class for conducting thermal throttling
+class ThermalThrottling {
+  public:
+    ThermalThrottling() = default;
+    ~ThermalThrottling() = default;
+    // Disallow copy and assign.
+    ThermalThrottling(const ThermalThrottling &) = delete;
+    void operator=(const ThermalThrottling &) = delete;
+
+    // Clear throttling data
+    void clearThrottlingData(std::string_view sensor_name, const SensorInfo &sensor_info);
+    // Register map for throttling algo
+    bool registerThermalThrottling(
+            std::string_view sensor_name, const std::shared_ptr<ThrottlingInfo> &throttling_info,
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
+    // Register map for throttling release algo
+    bool registerThrottlingReleaseToWatch(std::string_view sensor_name, std::string_view cdev_name,
+                                          const BindedCdevInfo &binded_cdev_info);
+    // Get throttling status map
+    const std::unordered_map<std::string, ThermalThrottlingStatus> &GetThermalThrottlingStatusMap()
+            const {
+        std::shared_lock<std::shared_mutex> _lock(thermal_throttling_status_map_mutex_);
+        return thermal_throttling_status_map_;
+    }
+    // Update thermal throttling request for the specific sensor
+    void thermalThrottlingUpdate(
+            const Temperature &temp, const SensorInfo &sensor_info,
+            const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
+            const std::unordered_map<std::string, PowerStatus> &power_status_map,
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
+
+    // Compute the throttling target from all the sensors' request
+    void computeCoolingDevicesRequest(std::string_view sensor_name, const SensorInfo &sensor_info,
+                                      const ThrottlingSeverity curr_severity,
+                                      std::vector<std::string> *cooling_devices_to_update,
+                                      ThermalStatsHelper *thermal_stats_helper);
+    // Get the aggregated (from all sensor) max request for a cooling device
+    bool getCdevMaxRequest(std::string_view cdev_name, int *max_state);
+
+  private:
+    // PID algo - get the total power budget
+    float updatePowerBudget(const Temperature &temp, const SensorInfo &sensor_info,
+                            std::chrono::milliseconds time_elapsed_ms,
+                            ThrottlingSeverity curr_severity);
+
+    // PID algo - return the power number from excluded power rail list
+    float computeExcludedPower(const SensorInfo &sensor_info,
+                               const ThrottlingSeverity curr_severity,
+                               const std::unordered_map<std::string, PowerStatus> &power_status_map,
+                               std::string *log_buf, std::string_view sensor_name);
+
+    // PID algo - allocate the power to target CDEV according to the ODPM
+    bool allocatePowerToCdev(
+            const Temperature &temp, const SensorInfo &sensor_info,
+            const ThrottlingSeverity curr_severity, const std::chrono::milliseconds time_elapsed_ms,
+            const std::unordered_map<std::string, PowerStatus> &power_status_map,
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
+    // PID algo - map the target throttling state according to the power budget
+    void updateCdevRequestByPower(
+            std::string sensor_name,
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map);
+    // Hard limit algo - assign the throttling state according to the severity
+    void updateCdevRequestBySeverity(std::string_view sensor_name, const SensorInfo &sensor_info,
+                                     ThrottlingSeverity curr_severity);
+    // Throttling release algo - decide release step according to the predefined power threshold,
+    // return false if the throttling release is not registered in thermal config
+    bool throttlingReleaseUpdate(
+            std::string_view sensor_name,
+            const std::unordered_map<std::string, CdevInfo> &cooling_device_info_map,
+            const std::unordered_map<std::string, PowerStatus> &power_status_map,
+            const ThrottlingSeverity severity, const SensorInfo &sensor_info);
+    // Update the cooling device request set for new request and notify the caller if there is
+    // change in max_request for the cooling device.
+    bool updateCdevMaxRequestAndNotifyIfChange(std::string_view cdev_name, int cur_request,
+                                               int new_request);
+    mutable std::shared_mutex thermal_throttling_status_map_mutex_;
+    // Thermal throttling status from each sensor
+    std::unordered_map<std::string, ThermalThrottlingStatus> thermal_throttling_status_map_;
+    std::shared_mutex cdev_all_request_map_mutex_;
+    // Set of all request for a cooling device from each sensor
+    std::unordered_map<std::string, std::multiset<int, std::greater<int>>> cdev_all_request_map_;
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_watcher.cpp b/aidl/thermal/utils/thermal_watcher.cpp
new file mode 100644 (file)
index 0000000..d8bc92e
--- /dev/null
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_THERMAL | ATRACE_TAG_HAL)
+
+#include "thermal_watcher.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <cutils/uevent.h>
+#include <dirent.h>
+#include <linux/netlink.h>
+#include <linux/thermal.h>
+#include <sys/inotify.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <utils/Trace.h>
+
+#include <chrono>
+#include <fstream>
+
+#include "../thermal-helper.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+namespace {
+
+static int nlErrorHandle(struct sockaddr_nl *nla, struct nlmsgerr *err, void *arg) {
+    int *ret = reinterpret_cast<int *>(arg);
+    *ret = err->error;
+    LOG(ERROR) << __func__ << "nl_groups: " << nla->nl_groups << ", nl_pid: " << nla->nl_pid;
+
+    return NL_STOP;
+}
+
+static int nlFinishHandle(struct nl_msg *msg, void *arg) {
+    int *ret = reinterpret_cast<int *>(arg);
+    *ret = 1;
+    struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+    LOG(VERBOSE) << __func__ << ": nlmsg type: " << nlh->nlmsg_type;
+
+    return NL_OK;
+}
+
+static int nlAckHandle(struct nl_msg *msg, void *arg) {
+    int *ret = reinterpret_cast<int *>(arg);
+    *ret = 1;
+    struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+    LOG(VERBOSE) << __func__ << ": nlmsg type: " << nlh->nlmsg_type;
+
+    return NL_OK;
+}
+
+static int nlSeqCheckHandle(struct nl_msg *msg, void *arg) {
+    int *ret = reinterpret_cast<int *>(arg);
+    *ret = 1;
+    struct nlmsghdr *nlh = nlmsg_hdr(msg);
+
+    LOG(VERBOSE) << __func__ << ": nlmsg type: " << nlh->nlmsg_type;
+
+    return NL_OK;
+}
+
+struct HandlerArgs {
+    const char *group;
+    int id;
+};
+
+static int nlSendMsg(struct nl_sock *sock, struct nl_msg *msg,
+                     int (*rx_handler)(struct nl_msg *, void *), void *data) {
+    int err, done = 0;
+
+    std::unique_ptr<nl_cb, decltype(&nl_cb_put)> cb(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
+
+    err = nl_send_auto_complete(sock, msg);
+    if (err < 0)
+        return err;
+
+    err = 0;
+    nl_cb_err(cb.get(), NL_CB_CUSTOM, nlErrorHandle, &err);
+    nl_cb_set(cb.get(), NL_CB_FINISH, NL_CB_CUSTOM, nlFinishHandle, &done);
+    nl_cb_set(cb.get(), NL_CB_ACK, NL_CB_CUSTOM, nlAckHandle, &done);
+
+    if (rx_handler != NULL)
+        nl_cb_set(cb.get(), NL_CB_VALID, NL_CB_CUSTOM, rx_handler, data);
+
+    while (err == 0 && done == 0) nl_recvmsgs(sock, cb.get());
+
+    return err;
+}
+
+static int nlFamilyHandle(struct nl_msg *msg, void *arg) {
+    struct HandlerArgs *grp = reinterpret_cast<struct HandlerArgs *>(arg);
+    struct nlattr *tb[CTRL_ATTR_MAX + 1];
+    struct genlmsghdr *gnlh = (struct genlmsghdr *)nlmsg_data(nlmsg_hdr(msg));
+    struct nlattr *mcgrp;
+    int rem_mcgrp;
+
+    nla_parse(tb, CTRL_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL);
+
+    if (!tb[CTRL_ATTR_MCAST_GROUPS]) {
+        LOG(ERROR) << __func__ << "Multicast group not found";
+        return -1;
+    }
+
+    nla_for_each_nested(mcgrp, tb[CTRL_ATTR_MCAST_GROUPS], rem_mcgrp) {
+        struct nlattr *tb_mcgrp[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+        nla_parse(tb_mcgrp, CTRL_ATTR_MCAST_GRP_MAX, reinterpret_cast<nlattr *>(nla_data(mcgrp)),
+                  nla_len(mcgrp), NULL);
+
+        if (!tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME] || !tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID])
+            continue;
+
+        if (strncmp(reinterpret_cast<char *>(nla_data(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])),
+                    grp->group, nla_len(tb_mcgrp[CTRL_ATTR_MCAST_GRP_NAME])) != 0)
+            continue;
+
+        grp->id = nla_get_u32(tb_mcgrp[CTRL_ATTR_MCAST_GRP_ID]);
+
+        break;
+    }
+
+    return 0;
+}
+
+static int nlGetMulticastId(struct nl_sock *sock, const char *family, const char *group) {
+    int err = 0, ctrlid;
+    struct HandlerArgs grp = {
+            .group = group,
+            .id = -ENOENT,
+    };
+
+    std::unique_ptr<nl_msg, decltype(&nlmsg_free)> msg(nlmsg_alloc(), nlmsg_free);
+
+    ctrlid = genl_ctrl_resolve(sock, "nlctrl");
+
+    genlmsg_put(msg.get(), 0, 0, ctrlid, 0, 0, CTRL_CMD_GETFAMILY, 0);
+
+    nla_put_string(msg.get(), CTRL_ATTR_FAMILY_NAME, family);
+
+    err = nlSendMsg(sock, msg.get(), nlFamilyHandle, &grp);
+    if (err)
+        return err;
+
+    err = grp.id;
+    LOG(INFO) << group << " multicast_id: " << grp.id;
+
+    return err;
+}
+
+static bool socketAddMembership(struct nl_sock *sock, const char *group) {
+    int mcid = nlGetMulticastId(sock, THERMAL_GENL_FAMILY_NAME, group);
+    if (mcid < 0) {
+        LOG(ERROR) << "Failed to get multicast id: " << group;
+        return false;
+    }
+
+    if (nl_socket_add_membership(sock, mcid)) {
+        LOG(ERROR) << "Failed to add netlink socket membership: " << group;
+        return false;
+    }
+
+    LOG(INFO) << "Added netlink socket membership: " << group;
+    return true;
+}
+
+static int handleEvent(struct nl_msg *n, void *arg) {
+    struct nlmsghdr *nlh = nlmsg_hdr(n);
+    struct genlmsghdr *glh = genlmsg_hdr(nlh);
+    struct nlattr *attrs[THERMAL_GENL_ATTR_MAX + 1];
+    int *tz_id = reinterpret_cast<int *>(arg);
+
+    genlmsg_parse(nlh, 0, attrs, THERMAL_GENL_ATTR_MAX, NULL);
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_UP) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_UP";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
+            LOG(INFO) << "Thermal zone trip id: "
+                      << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_DOWN) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_DOWN";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
+            LOG(INFO) << "Thermal zone trip id: "
+                      << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_GOV_CHANGE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_GOV_CHANGE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_GOV_NAME])
+            LOG(INFO) << "Governor name: " << nla_get_string(attrs[THERMAL_GENL_ATTR_GOV_NAME]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_CREATE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_CREATE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_TZ_NAME])
+            LOG(INFO) << "Thermal zone name: " << nla_get_string(attrs[THERMAL_GENL_ATTR_TZ_NAME]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_DELETE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_DELETE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_DISABLE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_DISABLE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_ENABLE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_ENABLE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_CHANGE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_CHANGE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
+            LOG(INFO) << "Trip id:: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE])
+            LOG(INFO) << "Trip type: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP])
+            LOG(INFO) << "Trip temp: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST])
+            LOG(INFO) << "Trip hyst: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_ADD) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_ADD";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID])
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
+            LOG(INFO) << "Trip id:: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE])
+            LOG(INFO) << "Trip type: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TYPE]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP])
+            LOG(INFO) << "Trip temp: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_TEMP]);
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST])
+            LOG(INFO) << "Trip hyst: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_HYST]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_TZ_TRIP_DELETE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_TZ_TRIP_DELETE";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID])
+            LOG(INFO) << "Trip id:: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TRIP_ID]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_CDEV_STATE_UPDATE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_CDEV_STATE_UPDATE";
+        if (attrs[THERMAL_GENL_ATTR_CDEV_ID])
+            LOG(INFO) << "Cooling device id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]);
+        if (attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE])
+            LOG(INFO) << "Cooling device current state: "
+                      << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_CUR_STATE]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_CDEV_ADD) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_CDEV_ADD";
+        if (attrs[THERMAL_GENL_ATTR_CDEV_NAME])
+            LOG(INFO) << "Cooling device name: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_NAME]);
+        if (attrs[THERMAL_GENL_ATTR_CDEV_ID])
+            LOG(INFO) << "Cooling device id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]);
+        if (attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE])
+            LOG(INFO) << "Cooling device max state: "
+                      << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_MAX_STATE]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_EVENT_CDEV_DELETE) {
+        LOG(INFO) << "THERMAL_GENL_EVENT_CDEV_DELETE";
+        if (attrs[THERMAL_GENL_ATTR_CDEV_ID])
+            LOG(INFO) << "Cooling device id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_CDEV_ID]);
+    }
+
+    if (glh->cmd == THERMAL_GENL_SAMPLING_TEMP) {
+        LOG(INFO) << "THERMAL_GENL_SAMPLING_TEMP";
+        if (attrs[THERMAL_GENL_ATTR_TZ_ID]) {
+            LOG(INFO) << "Thermal zone id: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+            *tz_id = nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_ID]);
+        }
+        if (attrs[THERMAL_GENL_ATTR_TZ_TEMP])
+            LOG(INFO) << "Thermal zone temp: " << nla_get_u32(attrs[THERMAL_GENL_ATTR_TZ_TEMP]);
+    }
+
+    return 0;
+}
+
+}  // namespace
+
+void ThermalWatcher::registerFilesToWatch(const std::set<std::string> &sensors_to_watch) {
+    LOG(INFO) << "Uevent register file to watch...";
+    monitored_sensors_.insert(sensors_to_watch.begin(), sensors_to_watch.end());
+
+    uevent_fd_.reset((TEMP_FAILURE_RETRY(uevent_open_socket(64 * 1024, true))));
+    if (uevent_fd_.get() < 0) {
+        LOG(ERROR) << "failed to open uevent socket";
+        return;
+    }
+
+    fcntl(uevent_fd_, F_SETFL, O_NONBLOCK);
+
+    looper_->addFd(uevent_fd_.get(), 0, ::android::Looper::EVENT_INPUT, nullptr, nullptr);
+    sleep_ms_ = std::chrono::milliseconds(0);
+    last_update_time_ = boot_clock::now();
+}
+
+void ThermalWatcher::registerFilesToWatchNl(const std::set<std::string> &sensors_to_watch) {
+    LOG(INFO) << "Thermal genl register file to watch...";
+    monitored_sensors_.insert(sensors_to_watch.begin(), sensors_to_watch.end());
+
+    sk_thermal = nl_socket_alloc();
+    if (!sk_thermal) {
+        LOG(ERROR) << "nl_socket_alloc failed";
+        return;
+    }
+
+    if (genl_connect(sk_thermal)) {
+        LOG(ERROR) << "genl_connect failed: sk_thermal";
+        return;
+    }
+
+    thermal_genl_fd_.reset(nl_socket_get_fd(sk_thermal));
+    if (thermal_genl_fd_.get() < 0) {
+        LOG(ERROR) << "Failed to create thermal netlink socket";
+        return;
+    }
+
+    if (!socketAddMembership(sk_thermal, THERMAL_GENL_EVENT_GROUP_NAME)) {
+        return;
+    }
+
+    /*
+     * Currently, only the update_temperature() will send thermal genl samlping events
+     * from kernel. To avoid thermal-hal busy because samlping events are sent
+     * too frequently, ignore thermal genl samlping events until we figure out how to use it.
+     *
+    if (!socketAddMembership(sk_thermal, THERMAL_GENL_SAMPLING_GROUP_NAME)) {
+        return;
+    }
+    */
+
+    fcntl(thermal_genl_fd_, F_SETFL, O_NONBLOCK);
+    looper_->addFd(thermal_genl_fd_.get(), 0, ::android::Looper::EVENT_INPUT, nullptr, nullptr);
+    sleep_ms_ = std::chrono::milliseconds(0);
+    last_update_time_ = boot_clock::now();
+}
+
+bool ThermalWatcher::startWatchingDeviceFiles() {
+    if (cb_) {
+        auto ret = this->run("FileWatcherThread", ::android::PRIORITY_HIGHEST);
+        if (ret != ::android::NO_ERROR) {
+            LOG(ERROR) << "ThermalWatcherThread start fail";
+            return false;
+        } else {
+            LOG(INFO) << "ThermalWatcherThread started";
+            return true;
+        }
+    }
+    return false;
+}
+void ThermalWatcher::parseUevent(std::set<std::string> *sensors_set) {
+    bool thermal_event = false;
+    constexpr int kUeventMsgLen = 2048;
+    char msg[kUeventMsgLen + 2];
+    char *cp;
+
+    while (true) {
+        int n = uevent_kernel_multicast_recv(uevent_fd_.get(), msg, kUeventMsgLen);
+        if (n <= 0) {
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                LOG(ERROR) << "Error reading from Uevent Fd";
+            }
+            break;
+        }
+
+        if (n >= kUeventMsgLen) {
+            LOG(ERROR) << "Uevent overflowed buffer, discarding";
+            continue;
+        }
+
+        msg[n] = '\0';
+        msg[n + 1] = '\0';
+
+        cp = msg;
+        while (*cp) {
+            std::string uevent = cp;
+            auto findSubSystemThermal = uevent.find("SUBSYSTEM=thermal");
+            if (!thermal_event) {
+                if (::android::base::StartsWith(uevent, "SUBSYSTEM=")) {
+                    if (findSubSystemThermal != std::string::npos) {
+                        thermal_event = true;
+                    } else {
+                        break;
+                    }
+                }
+            } else {
+                auto start_pos = uevent.find("NAME=");
+                if (start_pos != std::string::npos) {
+                    start_pos += 5;
+                    std::string name = uevent.substr(start_pos);
+                    if (monitored_sensors_.find(name) != monitored_sensors_.end()) {
+                        sensors_set->insert(name);
+                    }
+                    break;
+                }
+            }
+            while (*cp++) {
+            }
+        }
+    }
+}
+
+// TODO(b/175367921): Consider for potentially adding more type of event in the function
+// instead of just add the sensors to the list.
+void ThermalWatcher::parseGenlink(std::set<std::string> *sensors_set) {
+    int err = 0, done = 0, tz_id = -1;
+
+    std::unique_ptr<nl_cb, decltype(&nl_cb_put)> cb(nl_cb_alloc(NL_CB_DEFAULT), nl_cb_put);
+
+    nl_cb_err(cb.get(), NL_CB_CUSTOM, nlErrorHandle, &err);
+    nl_cb_set(cb.get(), NL_CB_FINISH, NL_CB_CUSTOM, nlFinishHandle, &done);
+    nl_cb_set(cb.get(), NL_CB_ACK, NL_CB_CUSTOM, nlAckHandle, &done);
+    nl_cb_set(cb.get(), NL_CB_SEQ_CHECK, NL_CB_CUSTOM, nlSeqCheckHandle, &done);
+    nl_cb_set(cb.get(), NL_CB_VALID, NL_CB_CUSTOM, handleEvent, &tz_id);
+
+    while (!done && !err) {
+        nl_recvmsgs(sk_thermal, cb.get());
+
+        if (tz_id < 0) {
+            break;
+        }
+
+        std::string name;
+        if (getThermalZoneTypeById(tz_id, &name) &&
+            monitored_sensors_.find(name) != monitored_sensors_.end()) {
+            sensors_set->insert(name);
+        }
+    }
+}
+
+void ThermalWatcher::wake() {
+    looper_->wake();
+}
+
+bool ThermalWatcher::threadLoop() {
+    LOG(VERBOSE) << "ThermalWatcher polling...";
+
+    int fd;
+    std::set<std::string> sensors;
+
+    auto time_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(boot_clock::now() -
+                                                                                 last_update_time_);
+
+    if (time_elapsed_ms < sleep_ms_ &&
+        looper_->pollOnce(sleep_ms_.count(), &fd, nullptr, nullptr) >= 0) {
+        ATRACE_NAME("ThermalWatcher::threadLoop - receive event");
+        if (fd != uevent_fd_.get() && fd != thermal_genl_fd_.get()) {
+            return true;
+        } else if (fd == thermal_genl_fd_.get()) {
+            parseGenlink(&sensors);
+        } else if (fd == uevent_fd_.get()) {
+            parseUevent(&sensors);
+        }
+        // Ignore cb_ if uevent is not from monitored sensors
+        if (sensors.size() == 0) {
+            return true;
+        }
+    }
+
+    sleep_ms_ = cb_(sensors);
+    last_update_time_ = boot_clock::now();
+    return true;
+}
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl
diff --git a/aidl/thermal/utils/thermal_watcher.h b/aidl/thermal/utils/thermal_watcher.h
new file mode 100644 (file)
index 0000000..8f8b398
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/chrono_utils.h>
+#include <android-base/unique_fd.h>
+#include <linux/genetlink.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+#include <utils/Looper.h>
+#include <utils/Thread.h>
+
+#include <chrono>
+#include <condition_variable>
+#include <future>
+#include <list>
+#include <mutex>
+#include <set>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace thermal {
+namespace implementation {
+
+using ::android::base::boot_clock;
+using ::android::base::unique_fd;
+using WatcherCallback = std::function<std::chrono::milliseconds(const std::set<std::string> &name)>;
+
+// A helper class for monitoring thermal files changes.
+class ThermalWatcher : public ::android::Thread {
+  public:
+    explicit ThermalWatcher(const WatcherCallback &cb)
+        : Thread(false), cb_(cb), looper_(new ::android::Looper(true)) {}
+    ~ThermalWatcher() = default;
+
+    // Disallow copy and assign.
+    ThermalWatcher(const ThermalWatcher &) = delete;
+    void operator=(const ThermalWatcher &) = delete;
+
+    // Start the thread and return true if it succeeds.
+    bool startWatchingDeviceFiles();
+    // Give the file watcher a list of files to start watching. This helper
+    // class will by default wait for modifications to the file with a looper.
+    // This should be called before starting watcher thread.
+    // For monitoring uevents.
+    void registerFilesToWatch(const std::set<std::string> &sensors_to_watch);
+    // For monitoring thermal genl events.
+    void registerFilesToWatchNl(const std::set<std::string> &sensors_to_watch);
+    // Wake up the looper thus the worker thread, immediately. This can be called
+    // in any thread.
+    void wake();
+
+  private:
+    // The work done by the watcher thread. This will use inotify to check for
+    // modifications to the files to watch. If any modification is seen this
+    // will callback the registered function with the new data read from the
+    // modified file.
+    bool threadLoop() override;
+
+    // Parse uevent message
+    void parseUevent(std::set<std::string> *sensor_name);
+
+    // Parse thermal netlink message
+    void parseGenlink(std::set<std::string> *sensor_name);
+
+    // Maps watcher filer descriptor to watched file path.
+    std::unordered_map<int, std::string> watch_to_file_path_map_;
+
+    // The callback function. Called whenever thermal uevent is seen.
+    // The function passed in should expect a string in the form (type).
+    // Where type is the name of the thermal zone that trigger a uevent notification.
+    // Callback will return thermal trigger status for next polling decision.
+    const WatcherCallback cb_;
+
+    ::android::sp<::android::Looper> looper_;
+
+    // For uevent socket registration.
+    ::android::base::unique_fd uevent_fd_;
+    // For thermal genl socket registration.
+    ::android::base::unique_fd thermal_genl_fd_;
+    // Sensor list which monitor flag is enabled.
+    std::set<std::string> monitored_sensors_;
+    // Sleep interval voting result
+    std::chrono::milliseconds sleep_ms_;
+    // Timestamp for last thermal update
+    boot_clock::time_point last_update_time_;
+    // For thermal genl socket object.
+    struct nl_sock *sk_thermal;
+};
+
+}  // namespace implementation
+}  // namespace thermal
+}  // namespace hardware
+}  // namespace android
+}  // namespace aidl