diff --git a/hidl/thermal/Android.bp b/hidl/thermal/Android.bp new file mode 100644 index 00000000..60d1c700 --- /dev/null +++ b/hidl/thermal/Android.bp @@ -0,0 +1,47 @@ +cc_binary { + name: "android.hardware.thermal@2.0-service.pixel", + defaults: [ + "hidl_defaults", + ], + vendor: true, + relative_install_path: "hw", + vintf_fragments: ["android.hardware.thermal@2.0-service.pixel.xml"], + init_rc: [ + "android.hardware.thermal@2.0-service.pixel.rc", + ], + srcs: [ + "service.cpp", + "Thermal.cpp", + "thermal-helper.cpp", + "utils/config_parser.cpp", + "utils/thermal_files.cpp", + "utils/thermal_watcher.cpp", + ], + static_libs: [ + "libjsoncpp", + ], + shared_libs: [ + "libbase", + "libcutils", + "libhidlbase", + "libhidltransport", + "libutils", + "android.hardware.thermal@1.0", + "android.hardware.thermal@2.0", + ], + cflags: [ + "-Wall", + "-Werror", + "-Wextra", + "-Wunused", + ], + tidy: true, + tidy_checks: [ + "android-*", + "cert-*", + "clang-analyzer-security*", + ], + tidy_flags: [ + "-warnings-as-errors=android-*,clang-analyzer-security*,cert-*" + ], +} diff --git a/hidl/thermal/Thermal.cpp b/hidl/thermal/Thermal.cpp new file mode 100644 index 00000000..bd47f6eb --- /dev/null +++ b/hidl/thermal/Thermal.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include +#include + +#include "Thermal.h" +#include "thermal-helper.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +namespace { + +using ::android::hardware::interfacesEqual; +using ::android::hardware::thermal::V1_0::ThermalStatus; +using ::android::hardware::thermal::V1_0::ThermalStatusCode; +using ::android::hidl::base::V1_0::IBase; + +template +Return setFailureAndCallback(T _hidl_cb, hidl_vec data, std::string_view debug_msg) { + ThermalStatus status; + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = debug_msg.data(); + _hidl_cb(status, data); + return Void(); +} + +template +Return setInitFailureAndCallback(T _hidl_cb, hidl_vec data) { + return setFailureAndCallback(_hidl_cb, data, "Failure initializing thermal HAL"); +} + +} // namespace + +// On init we will spawn a thread which will continually watch for +// throttling. When throttling is seen, if we have a callback registered +// the thread will call notifyThrottling() else it will log the dropped +// throttling event and do nothing. The thread is only killed when +// Thermal() is killed. +Thermal::Thermal() + : thermal_helper_( + std::bind(&Thermal::sendThermalChangedCallback, this, std::placeholders::_1)) {} + +// Methods from ::android::hardware::thermal::V1_0::IThermal. +Return Thermal::getTemperatures(getTemperatures_cb _hidl_cb) { + ThermalStatus status; + status.code = ThermalStatusCode::SUCCESS; + hidl_vec temperatures; + + if (!thermal_helper_.isInitializedOk()) { + LOG(ERROR) << "ThermalHAL not initialized properly."; + return setInitFailureAndCallback(_hidl_cb, temperatures); + } + + if (!thermal_helper_.fillTemperatures(&temperatures)) { + return setFailureAndCallback(_hidl_cb, temperatures, "Failed to read thermal sensors."); + } + + _hidl_cb(status, temperatures); + return Void(); +} + +Return Thermal::getCpuUsages(getCpuUsages_cb _hidl_cb) { + ThermalStatus status; + status.code = ThermalStatusCode::SUCCESS; + hidl_vec cpu_usages; + + if (!thermal_helper_.isInitializedOk()) { + return setInitFailureAndCallback(_hidl_cb, cpu_usages); + } + + if (!thermal_helper_.fillCpuUsages(&cpu_usages)) { + return setFailureAndCallback(_hidl_cb, cpu_usages, "Failed to get CPU usages."); + } + + _hidl_cb(status, cpu_usages); + return Void(); +} + +Return Thermal::getCoolingDevices(getCoolingDevices_cb _hidl_cb) { + ThermalStatus status; + status.code = ThermalStatusCode::SUCCESS; + hidl_vec cooling_devices; + + if (!thermal_helper_.isInitializedOk()) { + return setInitFailureAndCallback(_hidl_cb, cooling_devices); + } + _hidl_cb(status, cooling_devices); + return Void(); +} + +Return Thermal::getCurrentTemperatures(bool filterType, TemperatureType_2_0 type, + getCurrentTemperatures_cb _hidl_cb) { + ThermalStatus status; + status.code = ThermalStatusCode::SUCCESS; + hidl_vec temperatures; + + if (!thermal_helper_.isInitializedOk()) { + LOG(ERROR) << "ThermalHAL not initialized properly."; + return setInitFailureAndCallback(_hidl_cb, temperatures); + } + + if (!thermal_helper_.fillCurrentTemperatures(filterType, type, &temperatures)) { + return setFailureAndCallback(_hidl_cb, temperatures, "Failed to read thermal sensors."); + } + + _hidl_cb(status, temperatures); + return Void(); +} + +Return Thermal::getTemperatureThresholds(bool filterType, TemperatureType_2_0 type, + getTemperatureThresholds_cb _hidl_cb) { + ThermalStatus status; + status.code = ThermalStatusCode::SUCCESS; + hidl_vec temperatures; + + if (!thermal_helper_.isInitializedOk()) { + LOG(ERROR) << "ThermalHAL not initialized properly."; + return setInitFailureAndCallback(_hidl_cb, temperatures); + } + + if (!thermal_helper_.fillTemperatureThresholds(filterType, type, &temperatures)) { + return setFailureAndCallback(_hidl_cb, temperatures, "Failed to read thermal sensors."); + } + + _hidl_cb(status, temperatures); + return Void(); +} + +Return Thermal::getCurrentCoolingDevices(bool filterType, CoolingType type, + getCurrentCoolingDevices_cb _hidl_cb) { + ThermalStatus status; + status.code = ThermalStatusCode::SUCCESS; + hidl_vec cooling_devices; + + if (!thermal_helper_.isInitializedOk()) { + LOG(ERROR) << "ThermalHAL not initialized properly."; + return setInitFailureAndCallback(_hidl_cb, cooling_devices); + } + + if (!thermal_helper_.fillCurrentCoolingDevices(filterType, type, &cooling_devices)) { + return setFailureAndCallback(_hidl_cb, cooling_devices, "Failed to read thermal sensors."); + } + + _hidl_cb(status, cooling_devices); + return Void(); +} + +Return Thermal::registerThermalChangedCallback(const sp &callback, + bool filterType, TemperatureType_2_0 type, + registerThermalChangedCallback_cb _hidl_cb) { + ThermalStatus status; + if (callback == nullptr) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "Invalid nullptr callback"; + LOG(ERROR) << status.debugMessage; + _hidl_cb(status); + return Void(); + } else { + status.code = ThermalStatusCode::SUCCESS; + } + std::lock_guard _lock(thermal_callback_mutex_); + if (std::any_of(callbacks_.begin(), callbacks_.end(), [&](const CallbackSetting &c) { + return interfacesEqual(c.callback, callback); + })) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "Same callback registered already"; + LOG(ERROR) << status.debugMessage; + } else { + callbacks_.emplace_back(callback, filterType, type); + LOG(INFO) << "a callback has been registered to ThermalHAL, isFilter: " << filterType + << " Type: " << android::hardware::thermal::V2_0::toString(type); + } + _hidl_cb(status); + return Void(); +} + +Return Thermal::unregisterThermalChangedCallback( + const sp &callback, unregisterThermalChangedCallback_cb _hidl_cb) { + ThermalStatus status; + if (callback == nullptr) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "Invalid nullptr callback"; + LOG(ERROR) << status.debugMessage; + _hidl_cb(status); + return Void(); + } else { + status.code = ThermalStatusCode::SUCCESS; + } + bool removed = false; + std::lock_guard _lock(thermal_callback_mutex_); + callbacks_.erase( + std::remove_if(callbacks_.begin(), callbacks_.end(), + [&](const CallbackSetting &c) { + if (interfacesEqual(c.callback, callback)) { + LOG(INFO) + << "a callback has been unregistered to ThermalHAL, isFilter: " + << c.is_filter_type << " Type: " + << android::hardware::thermal::V2_0::toString(c.type); + removed = true; + return true; + } + return false; + }), + callbacks_.end()); + if (!removed) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "The callback was not registered before"; + LOG(ERROR) << status.debugMessage; + } + _hidl_cb(status); + return Void(); +} + +void Thermal::sendThermalChangedCallback(const std::vector &temps) { + std::lock_guard _lock(thermal_callback_mutex_); + for (auto &t : temps) { + LOG(INFO) << "Sending notification: " + << " Type: " << android::hardware::thermal::V2_0::toString(t.type) + << " Name: " << t.name << " CurrentValue: " << t.value << " ThrottlingStatus: " + << android::hardware::thermal::V2_0::toString(t.throttlingStatus); + callbacks_.erase( + std::remove_if(callbacks_.begin(), callbacks_.end(), + [&](const CallbackSetting &c) { + if (!c.is_filter_type || t.type == c.type) { + Return ret = c.callback->notifyThrottling(t); + return !ret.isOk(); + } + LOG(ERROR) + << "a Thermal callback is dead, removed from callback list."; + return false; + }), + callbacks_.end()); + } +} + +Return Thermal::debug(const hidl_handle &handle, const hidl_vec &) { + if (handle != nullptr && handle->numFds >= 1) { + int fd = handle->data[0]; + std::ostringstream dump_buf; + + if (!thermal_helper_.isInitializedOk()) { + dump_buf << "ThermalHAL not initialized properly." << std::endl; + } else { + { + hidl_vec temperatures; + dump_buf << "getTemperatures:" << std::endl; + if (!thermal_helper_.fillTemperatures(&temperatures)) { + dump_buf << "Failed to read thermal sensors." << std::endl; + } + + for (const auto &t : temperatures) { + dump_buf << " Type: " << android::hardware::thermal::V1_0::toString(t.type) + << " Name: " << t.name << " CurrentValue: " << t.currentValue + << " ThrottlingThreshold: " << t.throttlingThreshold + << " ShutdownThreshold: " << t.shutdownThreshold + << " VrThrottlingThreshold: " << t.vrThrottlingThreshold << std::endl; + } + } + { + hidl_vec cpu_usages; + dump_buf << "getCpuUsages:" << std::endl; + if (!thermal_helper_.fillCpuUsages(&cpu_usages)) { + dump_buf << "Failed to get CPU usages." << std::endl; + } + + for (const auto &usage : cpu_usages) { + dump_buf << " Name: " << usage.name << " Active: " << usage.active + << " Total: " << usage.total << " IsOnline: " << usage.isOnline + << std::endl; + } + } + { + dump_buf << "getCurrentTemperatures:" << std::endl; + hidl_vec temperatures; + if (!thermal_helper_.fillCurrentTemperatures(false, TemperatureType_2_0::SKIN, + &temperatures)) { + dump_buf << "Failed to getCurrentTemperatures." << std::endl; + } + + for (const auto &t : temperatures) { + dump_buf << " Type: " << android::hardware::thermal::V2_0::toString(t.type) + << " Name: " << t.name << " CurrentValue: " << t.value + << " ThrottlingStatus: " + << android::hardware::thermal::V2_0::toString(t.throttlingStatus) + << std::endl; + } + } + { + dump_buf << "getTemperatureThresholds:" << std::endl; + hidl_vec temperatures; + if (!thermal_helper_.fillTemperatureThresholds(false, TemperatureType_2_0::SKIN, + &temperatures)) { + dump_buf << "Failed to getTemperatureThresholds." << std::endl; + } + + for (const auto &t : temperatures) { + dump_buf << " Type: " << android::hardware::thermal::V2_0::toString(t.type) + << " Name: " << t.name; + dump_buf << " hotThrottlingThreshold: ["; + for (size_t i = 0; i < kThrottlingSeverityCount; ++i) { + dump_buf << t.hotThrottlingThresholds[i] << " "; + } + dump_buf << "] coldThrottlingThreshold: ["; + for (size_t i = 0; i < kThrottlingSeverityCount; ++i) { + dump_buf << t.coldThrottlingThresholds[i] << " "; + } + dump_buf << "] vrThrottlingThreshold: " << t.vrThrottlingThreshold; + dump_buf << std::endl; + } + } + { + dump_buf << "getCurrentCoolingDevices:" << std::endl; + hidl_vec 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: " << android::hardware::thermal::V2_0::toString(c.type) + << " Name: " << c.name << " CurrentValue: " << c.value << std::endl; + } + } + { + dump_buf << "Callbacks: Total " << callbacks_.size() << std::endl; + for (const auto &c : callbacks_) { + dump_buf << " IsFilter: " << c.is_filter_type + << " Type: " << android::hardware::thermal::V2_0::toString(c.type) + << std::endl; + } + } + { + dump_buf << "getHysteresis:" << std::endl; + const auto &map = thermal_helper_.GetSensorInfoMap(); + for (const auto &name_info_pair : map) { + 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 << "Monitor:" << std::endl; + const auto &map = thermal_helper_.GetSensorInfoMap(); + for (const auto &name_info_pair : map) { + dump_buf << " Name: " << name_info_pair.first; + dump_buf << " Monitor: " << std::boolalpha << name_info_pair.second.is_monitor + << std::noboolalpha << std::endl; + } + } + } + std::string buf = dump_buf.str(); + if (!android::base::WriteStringToFd(buf, fd)) { + PLOG(ERROR) << "Failed to dump state to fd"; + } + fsync(fd); + } + return Void(); +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/hidl/thermal/Thermal.h b/hidl/thermal/Thermal.h new file mode 100644 index 00000000..317936f5 --- /dev/null +++ b/hidl/thermal/Thermal.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2018 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. + */ +#ifndef ANDROID_HARDWARE_THERMAL_V2_0_CROSSHATCH_THERMAL_H +#define ANDROID_HARDWARE_THERMAL_V2_0_CROSSHATCH_THERMAL_H + +#include +#include + +#include +#include +#include + +#include "thermal-helper.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::thermal::V2_0::IThermal; +using ::android::hardware::thermal::V2_0::IThermalChangedCallback; + +struct CallbackSetting { + CallbackSetting(sp callback, bool is_filter_type, + TemperatureType_2_0 type) + : callback(callback), is_filter_type(is_filter_type), type(type) {} + sp callback; + bool is_filter_type; + TemperatureType_2_0 type; +}; + +class Thermal : public IThermal { + public: + Thermal(); + ~Thermal() = default; + + // Disallow copy and assign. + Thermal(const Thermal &) = delete; + void operator=(const Thermal &) = delete; + + // Methods from ::android::hardware::thermal::V1_0::IThermal. + Return getTemperatures(getTemperatures_cb _hidl_cb) override; + Return getCpuUsages(getCpuUsages_cb _hidl_cb) override; + Return getCoolingDevices(getCoolingDevices_cb _hidl_cb) override; + + // Methods from ::android::hardware::thermal::V2_0::IThermal follow. + Return getCurrentTemperatures(bool filterType, TemperatureType_2_0 type, + getCurrentTemperatures_cb _hidl_cb) override; + Return getTemperatureThresholds(bool filterType, TemperatureType_2_0 type, + getTemperatureThresholds_cb _hidl_cb) override; + Return registerThermalChangedCallback(const sp &callback, + bool filterType, TemperatureType_2_0 type, + registerThermalChangedCallback_cb _hidl_cb) override; + Return unregisterThermalChangedCallback( + const sp &callback, + unregisterThermalChangedCallback_cb _hidl_cb) override; + Return getCurrentCoolingDevices(bool filterType, CoolingType type, + getCurrentCoolingDevices_cb _hidl_cb) override; + + // Methods from ::android::hidl::base::V1_0::IBase follow. + Return debug(const hidl_handle &fd, const hidl_vec &args) override; + + // Helper function for calling callbacks + void sendThermalChangedCallback(const std::vector &temps); + + private: + ThermalHelper thermal_helper_; + std::mutex thermal_callback_mutex_; + std::vector callbacks_; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_THERMAL_V2_0_CROSSHATCH_THERMAL_H diff --git a/hidl/thermal/android.hardware.thermal@2.0-service.pixel.rc b/hidl/thermal/android.hardware.thermal@2.0-service.pixel.rc new file mode 100644 index 00000000..27fc14a3 --- /dev/null +++ b/hidl/thermal/android.hardware.thermal@2.0-service.pixel.rc @@ -0,0 +1,5 @@ +service vendor.thermal-hal-2-0 /vendor/bin/hw/android.hardware.thermal@2.0-service.pixel + interface android.hardware.thermal@2.0::IThermal default + class hal + user system + group system diff --git a/hidl/thermal/android.hardware.thermal@2.0-service.pixel.xml b/hidl/thermal/android.hardware.thermal@2.0-service.pixel.xml new file mode 100644 index 00000000..bcd6344b --- /dev/null +++ b/hidl/thermal/android.hardware.thermal@2.0-service.pixel.xml @@ -0,0 +1,12 @@ + + + android.hardware.thermal + hwbinder + 1.0 + 2.0 + + IThermal + default + + + diff --git a/hidl/thermal/service.cpp b/hidl/thermal/service.cpp new file mode 100644 index 00000000..190269b7 --- /dev/null +++ b/hidl/thermal/service.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include "Thermal.h" + +using ::android::OK; +using ::android::status_t; + +// libhwbinder: +using ::android::hardware::configureRpcThreadpool; +using ::android::hardware::joinRpcThreadpool; + +// Generated HIDL files: +using ::android::hardware::thermal::V2_0::IThermal; +using ::android::hardware::thermal::V2_0::implementation::Thermal; + +static int shutdown() { + LOG(ERROR) << "Pixel Thermal HAL Service is shutting down."; + return 1; +} + +int main(int /* argc */, char ** /* argv */) { + status_t status; + android::sp service = nullptr; + + LOG(INFO) << "Pixel Thermal HAL Service 2.0 starting..."; + + service = new Thermal(); + if (service == nullptr) { + LOG(ERROR) << "Error creating an instance of Thermal HAL. Exiting..."; + return shutdown(); + } + + configureRpcThreadpool(1, true /* callerWillJoin */); + + status = service->registerAsService(); + if (status != OK) { + LOG(ERROR) << "Could not register service for ThermalHAL (" << status << ")"; + return shutdown(); + } + + LOG(INFO) << "Pixel Thermal HAL Service 2.0 started successfully."; + joinRpcThreadpool(); + // We should not get past the joinRpcThreadpool(). + return shutdown(); +} diff --git a/hidl/thermal/thermal-helper.cpp b/hidl/thermal/thermal-helper.cpp new file mode 100644 index 00000000..732d1817 --- /dev/null +++ b/hidl/thermal/thermal-helper.cpp @@ -0,0 +1,631 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "thermal-helper.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +constexpr std::string_view kCpuOnlineRoot("/sys/devices/system/cpu"); +constexpr std::string_view kThermalSensorsRoot("/sys/devices/virtual/thermal"); +constexpr std::string_view kCpuUsageFile("/proc/stat"); +constexpr std::string_view kCpuOnlineFileSuffix("online"); +constexpr std::string_view kCpuPresentFile("/sys/devices/system/cpu/present"); +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 kConfigProperty("vendor.thermal.config"); +constexpr std::string_view kConfigDefaultFileName("thermal_info_config.json"); + +namespace { +using android::base::StringPrintf; + +/* + * Pixel don't offline CPU, so std::thread::hardware_concurrency(); should work. + * However /sys/devices/system/cpu/present is preferred. + * The file is expected to contain single text line with two numbers %d-%d, + * which is a range of available cpu numbers, e.g. 0-7 would mean there + * are 8 cores number from 0 to 7. + * For Android systems this approach is safer than using cpufeatures, see bug + * b/36941727. + */ +std::size_t getNumberOfCores() { + std::string file; + if (!android::base::ReadFileToString(kCpuPresentFile.data(), &file)) { + LOG(ERROR) << "Error reading Cpu present file: " << kCpuPresentFile; + return 0; + } + std::vector pieces = android::base::Split(file, "-"); + if (pieces.size() != 2) { + LOG(ERROR) << "Error parsing Cpu present file content: " << file; + return 0; + } + auto min_core = std::stoul(pieces[0]); + auto max_core = std::stoul(pieces[1]); + if (max_core < min_core) { + LOG(ERROR) << "Error parsing Cpu present min and max: " << min_core << " - " << max_core; + return 0; + } + return static_cast(max_core - min_core + 1); +} +const std::size_t kMaxCpus = getNumberOfCores(); + +void parseCpuUsagesFileAndAssignUsages(hidl_vec *cpu_usages) { + uint64_t cpu_num, user, nice, system, idle; + std::string cpu_name; + std::string data; + if (!android::base::ReadFileToString(kCpuUsageFile.data(), &data)) { + LOG(ERROR) << "Error reading Cpu usage file: " << kCpuUsageFile; + return; + } + + std::istringstream stat_data(data); + std::string line; + while (std::getline(stat_data, line)) { + if (line.find("cpu") == 0 && isdigit(line[3])) { + // Split the string using spaces. + std::vector words = android::base::Split(line, " "); + cpu_name = words[0]; + cpu_num = std::stoi(cpu_name.substr(3)); + + if (cpu_num < kMaxCpus) { + user = std::stoi(words[1]); + nice = std::stoi(words[2]); + system = std::stoi(words[3]); + idle = std::stoi(words[4]); + + // Check if the CPU is online by reading the online file. + std::string cpu_online_path = + StringPrintf("%s/%s/%s", kCpuOnlineRoot.data(), cpu_name.c_str(), + kCpuOnlineFileSuffix.data()); + std::string is_online; + if (!android::base::ReadFileToString(cpu_online_path, &is_online)) { + LOG(ERROR) << "Could not open Cpu online file: " << cpu_online_path; + return; + } + is_online = android::base::Trim(is_online); + + (*cpu_usages)[cpu_num].name = cpu_name; + (*cpu_usages)[cpu_num].active = user + nice + system; + (*cpu_usages)[cpu_num].total = user + nice + system + idle; + (*cpu_usages)[cpu_num].isOnline = (is_online == "1") ? true : false; + } else { + LOG(ERROR) << "Unexpected cpu number: " << words[0]; + return; + } + } + } +} + +std::map parseThermalPathMap(std::string_view prefix) { + std::map path_map; + std::unique_ptr dir(opendir(kThermalSensorsRoot.data()), closedir); + if (!dir) { + return path_map; + } + + // std::filesystem is not available for vendor yet + // see discussion: aosp/894015 + while (struct dirent *dp = readdir(dir.get())) { + if (dp->d_type != DT_DIR) { + continue; + } + + if (!android::base::StartsWith(dp->d_name, prefix.data())) { + continue; + } + + std::string path = android::base::StringPrintf("%s/%s/%s", kThermalSensorsRoot.data(), + dp->d_name, kThermalNameFile.data()); + std::string name; + if (!android::base::ReadFileToString(path, &name)) { + PLOG(ERROR) << "Failed to read from " << path; + continue; + } + + path_map.emplace( + android::base::Trim(name), + android::base::StringPrintf("%s/%s", kThermalSensorsRoot.data(), dp->d_name)); + } + + return path_map; +} + +} // namespace + +/* + * Populate the sensor_name_to_file_map_ map by walking through the file tree, + * reading the type file and assigning the temp file path to the map. If we do + * not succeed, abort. + */ +ThermalHelper::ThermalHelper(const NotificationCallback &cb) + : thermal_watcher_(new ThermalWatcher( + std::bind(&ThermalHelper::thermalWatcherCallbackFunc, this, std::placeholders::_1))), + cb_(cb), + cooling_device_info_map_(ParseCoolingDevice( + "/vendor/etc/" + + android::base::GetProperty(kConfigProperty.data(), kConfigDefaultFileName.data()))), + sensor_info_map_(ParseSensorInfo( + "/vendor/etc/" + + android::base::GetProperty(kConfigProperty.data(), kConfigDefaultFileName.data()))) { + 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, + }; + } + + auto tz_map = parseThermalPathMap(kSensorPrefix.data()); + auto cdev_map = parseThermalPathMap(kCoolingDevicePrefix.data()); + + is_initialized_ = initializeSensorMap(tz_map) && initializeCoolingDevices(cdev_map); + if (!is_initialized_) { + LOG(FATAL) << "ThermalHAL could not be initialized properly."; + } + std::set cdev_paths; + std::transform(cooling_device_info_map_.cbegin(), cooling_device_info_map_.cend(), + std::inserter(cdev_paths, cdev_paths.begin()), + [this](std::pair const &cdev) { + std::string path = + cooling_devices_.getThermalFilePath(std::string_view(cdev.first)); + if (!path.empty()) + return path; + else + return std::string(); + }); + std::set monitored_sensors; + std::transform(sensor_info_map_.cbegin(), sensor_info_map_.cend(), + std::inserter(monitored_sensors, monitored_sensors.begin()), + [](std::pair const &sensor) { + if (sensor.second.is_monitor) + return sensor.first; + else + return std::string(); + }); + + thermal_watcher_->registerFilesToWatch(monitored_sensors, cdev_paths, initializeTrip(tz_map)); + + // Need start watching after status map initialized + is_initialized_ = thermal_watcher_->startWatchingDeviceFiles(); + if (!is_initialized_) { + LOG(FATAL) << "ThermalHAL could not start watching thread properly."; + } +} + +bool ThermalHelper::readCoolingDevice(std::string_view cooling_device, + CoolingDevice_2_0 *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 CoolingType &type = cooling_device_info_map_.at(cooling_device.data()); + + out->type = type; + out->name = cooling_device.data(); + out->value = std::stoi(data); + + return true; +} + +bool ThermalHelper::readTemperature(std::string_view sensor_name, Temperature_1_0 *out) const { + // Read the file. If the file can't be read temp will be empty string. + std::string temp; + + if (!thermal_sensors_.readThermalFile(sensor_name, &temp)) { + LOG(ERROR) << "readTemperature: sensor not found: " << sensor_name; + return false; + } + + if (temp.empty()) { + LOG(ERROR) << "readTemperature: failed to read sensor: " << sensor_name; + return false; + } + + const SensorInfo &sensor_info = sensor_info_map_.at(sensor_name.data()); + TemperatureType_1_0 type = + (static_cast(sensor_info.type) > static_cast(TemperatureType_1_0::SKIN)) + ? TemperatureType_1_0::UNKNOWN + : static_cast(sensor_info.type); + out->type = type; + out->name = sensor_name.data(); + out->currentValue = std::stof(temp) * sensor_info.multiplier; + out->throttlingThreshold = + sensor_info.hot_thresholds[static_cast(ThrottlingSeverity::SEVERE)]; + out->shutdownThreshold = + sensor_info.hot_thresholds[static_cast(ThrottlingSeverity::SHUTDOWN)]; + out->vrThrottlingThreshold = sensor_info.vr_threshold; + + return true; +} + +bool ThermalHelper::readTemperature( + std::string_view sensor_name, Temperature_2_0 *out, + std::pair *throtting_status) const { + // Read the file. If the file can't be read temp will be empty string. + std::string temp; + + if (!thermal_sensors_.readThermalFile(sensor_name, &temp)) { + LOG(ERROR) << "readTemperature: sensor not found: " << sensor_name; + return false; + } + + if (temp.empty()) { + 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 = std::stof(temp) * sensor_info.multiplier; + + std::pair status = + std::make_pair(ThrottlingSeverity::NONE, ThrottlingSeverity::NONE); + // Only update status if the thermal sensor is being monitored + if (sensor_info.is_monitor) { + ThrottlingSeverity prev_hot_severity, prev_cold_severity; + { + // reader lock, readTemperature will be called in Binder call and the watcher thread. + std::shared_lock _lock(sensor_status_map_mutex_); + prev_hot_severity = sensor_status_map_.at(sensor_name.data()).prev_hot_severity; + prev_cold_severity = sensor_status_map_.at(sensor_name.data()).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 (throtting_status) { + *throtting_status = status; + } + + out->throttlingStatus = static_cast(status.first) > static_cast(status.second) + ? status.first + : status.second; + + 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 = sensor_info.hot_thresholds; + out->coldThrottlingThresholds = sensor_info.cold_thresholds; + out->vrThrottlingThreshold = sensor_info.vr_threshold; + return true; +} + +std::pair ThermalHelper::getSeverityFromThresholds( + const ThrottlingArray &hot_thresholds, const ThrottlingArray &cold_thresholds, + const ThrottlingArray &hot_hysteresis, const ThrottlingArray &cold_hysteresis, + ThrottlingSeverity prev_hot_severity, ThrottlingSeverity prev_cold_severity, + float value) const { + ThrottlingSeverity ret_hot = ThrottlingSeverity::NONE; + ThrottlingSeverity ret_hot_hysteresis = ThrottlingSeverity::NONE; + ThrottlingSeverity ret_cold = ThrottlingSeverity::NONE; + ThrottlingSeverity ret_cold_hysteresis = ThrottlingSeverity::NONE; + + // Here we want to control the iteration from high to low, and hidl_enum_range doesn't support + // a reverse iterator yet. + for (size_t i = static_cast(ThrottlingSeverity::SHUTDOWN); + i > static_cast(ThrottlingSeverity::NONE); --i) { + if (!std::isnan(hot_thresholds[i]) && hot_thresholds[i] <= value && + ret_hot == ThrottlingSeverity::NONE) { + ret_hot = static_cast(i); + } + if (!std::isnan(hot_thresholds[i]) && (hot_thresholds[i] - hot_hysteresis[i]) < value && + ret_hot_hysteresis == ThrottlingSeverity::NONE) { + ret_hot_hysteresis = static_cast(i); + } + if (!std::isnan(cold_thresholds[i]) && cold_thresholds[i] >= value && + ret_cold == ThrottlingSeverity::NONE) { + ret_cold = static_cast(i); + } + if (!std::isnan(cold_thresholds[i]) && (cold_thresholds[i] + cold_hysteresis[i]) > value && + ret_cold_hysteresis == ThrottlingSeverity::NONE) { + ret_cold_hysteresis = static_cast(i); + } + } + if (static_cast(ret_hot) < static_cast(prev_hot_severity)) { + ret_hot = ret_hot_hysteresis; + } + if (static_cast(ret_cold) < static_cast(prev_cold_severity)) { + ret_cold = ret_cold_hysteresis; + } + + return std::make_pair(ret_hot, ret_cold); +} + +bool ThermalHelper::initializeSensorMap(const std::map &path_map) { + for (const auto &sensor_info_pair : sensor_info_map_) { + std::string_view sensor_name = sensor_info_pair.first; + if (!path_map.count(sensor_name.data())) { + LOG(ERROR) << "Could not find " << sensor_name << " in sysfs"; + continue; + } + std::string path = android::base::StringPrintf( + "%s/%s", path_map.at(sensor_name.data()).c_str(), kSensorTempSuffix.data()); + if (!thermal_sensors_.addThermalFile(sensor_name, path)) { + LOG(ERROR) << "Could not add " << sensor_name << "to sensors map"; + } + } + if (sensor_info_map_.size() == thermal_sensors_.getNumThermalFiles()) { + return true; + } + return false; +} + +bool ThermalHelper::initializeCoolingDevices(const std::map &path_map) { + for (const auto &cooling_device_info_pair : cooling_device_info_map_) { + std::string_view cooling_device_name = cooling_device_info_pair.first; + if (!path_map.count(cooling_device_name.data())) { + LOG(ERROR) << "Could not find " << cooling_device_name << " in sysfs"; + continue; + } + std::string path = android::base::StringPrintf( + "%s/%s", path_map.at(cooling_device_name.data()).c_str(), + kCoolingDeviceCurStateSuffix.data()); + if (!cooling_devices_.addThermalFile(cooling_device_name, path)) { + LOG(ERROR) << "Could not add " << cooling_device_name << "to cooling device map"; + continue; + } + } + + if (cooling_device_info_map_.size() == cooling_devices_.getNumThermalFiles()) { + return true; + } + return false; +} + +bool ThermalHelper::initializeTrip(const std::map &path_map) { + for (const auto &sensor_info : sensor_info_map_) { + if (sensor_info.second.is_monitor) { + 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 (!android::base::ReadFileToString(path, &tz_policy)) { + LOG(ERROR) << sensor_name << " could not open tz policy file:" << path; + return false; + } + // Check if thermal zone support uevent notify + tz_policy = android::base::Trim(tz_policy); + if (tz_policy != kUserSpaceSuffix) { + LOG(ERROR) << sensor_name << " does not support uevent notify"; + return false; + } + + // Update thermal zone trip point + for (size_t i = 0; i < kThrottlingSeverityCount; ++i) { + if (!std::isnan(sensor_info.second.hot_thresholds[i]) && + !std::isnan(sensor_info.second.hot_hysteresis[i])) { + // Update trip_point_0_temp threshold + std::string threshold = std::to_string(static_cast( + sensor_info.second.hot_thresholds[i] / sensor_info.second.multiplier)); + path = android::base::StringPrintf("%s/%s", (tz_path.data()), + kSensorTripPointTempZeroFile.data()); + if (!android::base::WriteStringToFile(threshold, path)) { + LOG(ERROR) << "fail to update " << sensor_name + << " trip point: " << threshold << path; + return false; + } + // Update trip_point_0_hyst threshold + threshold = std::to_string(static_cast( + sensor_info.second.hot_hysteresis[i] / sensor_info.second.multiplier)); + path = android::base::StringPrintf("%s/%s", (tz_path.data()), + kSensorTripPointHystZeroFile.data()); + if (!android::base::WriteStringToFile(threshold, path)) { + LOG(ERROR) << "fail to update " << sensor_name << "trip hyst" << threshold + << path; + return false; + } + break; + } else if (i == kThrottlingSeverityCount - 1) { + LOG(ERROR) << sensor_name << ":all thresholds are NAN"; + return false; + } + } + } + } + return true; +} +bool ThermalHelper::fillTemperatures(hidl_vec *temperatures) const { + temperatures->resize(sensor_info_map_.size()); + int current_index = 0; + for (const auto &name_info_pair : sensor_info_map_) { + Temperature_1_0 temp; + + if (readTemperature(name_info_pair.first, &temp)) { + (*temperatures)[current_index] = temp; + } else { + LOG(ERROR) << __func__ + << ": error reading temperature for sensor: " << name_info_pair.first; + return false; + } + ++current_index; + } + return current_index > 0; +} + +bool ThermalHelper::fillCurrentTemperatures(bool filterType, TemperatureType_2_0 type, + hidl_vec *temperatures) const { + std::vector ret; + for (const auto &name_info_pair : sensor_info_map_) { + Temperature_2_0 temp; + if (filterType && name_info_pair.second.type != type) { + continue; + } + if (readTemperature(name_info_pair.first, &temp)) { + ret.emplace_back(std::move(temp)); + } else { + LOG(ERROR) << __func__ + << ": error reading temperature for sensor: " << name_info_pair.first; + return false; + } + } + *temperatures = ret; + return ret.size() > 0; +} + +bool ThermalHelper::fillTemperatureThresholds(bool filterType, TemperatureType_2_0 type, + hidl_vec *thresholds) const { + std::vector ret; + for (const auto &name_info_pair : sensor_info_map_) { + TemperatureThreshold temp; + 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, + hidl_vec *cooling_devices) const { + std::vector ret; + for (const auto &name_info_pair : cooling_device_info_map_) { + CoolingDevice_2_0 value; + if (filterType && name_info_pair.second != 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::fillCpuUsages(hidl_vec *cpu_usages) const { + cpu_usages->resize(kMaxCpus); + parseCpuUsagesFileAndAssignUsages(cpu_usages); + 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. +bool ThermalHelper::thermalWatcherCallbackFunc(const std::set &uevent_sensors) { + std::vector temps; + bool thermal_triggered = false; + for (auto &name_status_pair : sensor_status_map_) { + Temperature_2_0 temp; + TemperatureThreshold threshold; + SensorStatus &sensor_status = name_status_pair.second; + const SensorInfo &sensor_info = sensor_info_map_.at(name_status_pair.first); + // Only send notification on whitelisted sensors + if (!sensor_info.is_monitor) { + continue; + } + // If callback is triggered by uevent, only check the sensors within uevent_sensors + if (uevent_sensors.size() != 0 && + uevent_sensors.find(name_status_pair.first) == uevent_sensors.end()) { + if (sensor_status.severity != ThrottlingSeverity::NONE) { + thermal_triggered = true; + } + continue; + } + + std::pair throtting_status; + if (!readTemperature(name_status_pair.first, &temp, &throtting_status)) { + LOG(ERROR) << __func__ + << ": error reading temperature for sensor: " << name_status_pair.first; + continue; + } + if (!readTemperatureThreshold(name_status_pair.first, &threshold)) { + LOG(ERROR) << __func__ << ": error reading temperature threshold for sensor: " + << name_status_pair.first; + continue; + } + + { + // writer lock + std::unique_lock _lock(sensor_status_map_mutex_); + if (throtting_status.first != sensor_status.prev_hot_severity) { + sensor_status.prev_hot_severity = throtting_status.first; + } + if (throtting_status.second != sensor_status.prev_cold_severity) { + sensor_status.prev_cold_severity = throtting_status.second; + } + if (temp.throttlingStatus != sensor_status.severity) { + temps.push_back(temp); + sensor_status.severity = temp.throttlingStatus; + } + } + if (sensor_status.severity != ThrottlingSeverity::NONE) { + thermal_triggered = true; + } + } + if (!temps.empty() && cb_) { + cb_(temps); + } + + return thermal_triggered; +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/hidl/thermal/thermal-helper.h b/hidl/thermal/thermal-helper.h new file mode 100644 index 00000000..5eb389ee --- /dev/null +++ b/hidl/thermal/thermal-helper.h @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of The Linux Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THERMAL_THERMAL_HELPER_H__ +#define THERMAL_THERMAL_HELPER_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "utils/config_parser.h" +#include "utils/thermal_files.h" +#include "utils/thermal_watcher.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::hardware::hidl_vec; +using ::android::hardware::thermal::V1_0::CpuUsage; +using ::android::hardware::thermal::V2_0::CoolingType; +using ::android::hardware::thermal::V2_0::IThermal; +using CoolingDevice_1_0 = ::android::hardware::thermal::V1_0::CoolingDevice; +using CoolingDevice_2_0 = ::android::hardware::thermal::V2_0::CoolingDevice; +using Temperature_1_0 = ::android::hardware::thermal::V1_0::Temperature; +using Temperature_2_0 = ::android::hardware::thermal::V2_0::Temperature; +using TemperatureType_1_0 = ::android::hardware::thermal::V1_0::TemperatureType; +using TemperatureType_2_0 = ::android::hardware::thermal::V2_0::TemperatureType; +using ::android::hardware::thermal::V2_0::TemperatureThreshold; +using ::android::hardware::thermal::V2_0::ThrottlingSeverity; + +using NotificationCallback = std::function &temps)>; +using NotificationTime = std::chrono::time_point; + +struct SensorStatus { + ThrottlingSeverity severity; + ThrottlingSeverity prev_hot_severity; + ThrottlingSeverity prev_cold_severity; +}; + +class ThermalHelper { + public: + ThermalHelper(const NotificationCallback &cb); + ~ThermalHelper() = default; + + bool fillTemperatures(hidl_vec *temperatures) const; + bool fillCurrentTemperatures(bool filterType, TemperatureType_2_0 type, + hidl_vec *temperatures) const; + bool fillTemperatureThresholds(bool filterType, TemperatureType_2_0 type, + hidl_vec *thresholds) const; + bool fillCurrentCoolingDevices(bool filterType, CoolingType type, + hidl_vec *coolingdevices) const; + bool fillCpuUsages(hidl_vec *cpu_usages) const; + + // Dissallow 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_1_0 *out) const; + bool readTemperature( + std::string_view sensor_name, Temperature_2_0 *out, + std::pair *throtting_status = nullptr) const; + 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_2_0 *out) const; + // Get SensorInfo Map + const std::map &GetSensorInfoMap() const { return sensor_info_map_; } + + private: + bool initializeSensorMap(const std::map &path_map); + bool initializeCoolingDevices(const std::map &path_map); + bool initializeTrip(const std::map &path_map); + + // For thermal_watcher_'s polling thread + bool thermalWatcherCallbackFunc(const std::set &uevent_sensors); + // Return hot and cold severity status as std::pair + std::pair getSeverityFromThresholds( + const ThrottlingArray &hot_thresholds, const ThrottlingArray &cold_thresholds, + const ThrottlingArray &hot_hysteresis, const ThrottlingArray &cold_hysteresis, + ThrottlingSeverity prev_hot_severity, ThrottlingSeverity prev_cold_severity, + float value) const; + + sp thermal_watcher_; + ThermalFiles thermal_sensors_; + ThermalFiles cooling_devices_; + bool is_initialized_; + const NotificationCallback cb_; + const std::map cooling_device_info_map_; + const std::map sensor_info_map_; + + mutable std::shared_mutex sensor_status_map_mutex_; + std::map sensor_status_map_; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif // THERMAL_THERMAL_HELPER_H__ diff --git a/hidl/thermal/utils/config_parser.cpp b/hidl/thermal/utils/config_parser.cpp new file mode 100644 index 00000000..d74fd495 --- /dev/null +++ b/hidl/thermal/utils/config_parser.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include + +#include +#include + +#include "config_parser.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::hardware::hidl_enum_range; +using ::android::hardware::thermal::V2_0::toString; +using TemperatureType_2_0 = ::android::hardware::thermal::V2_0::TemperatureType; + +namespace { + +template +// Return false when failed parsing +bool getTypeFromString(std::string_view str, T *out) { + auto types = hidl_enum_range(); + for (const auto &type : types) { + if (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(); + } +} + +} // namespace + +std::map ParseSensorInfo(std::string_view config_path) { + std::string json_doc; + std::map sensors_parsed; + if (!android::base::ReadFileToString(config_path.data(), &json_doc)) { + LOG(ERROR) << "Failed to read JSON config from " << config_path; + return sensors_parsed; + } + + Json::Value root; + Json::Reader reader; + + if (!reader.parse(json_doc, root)) { + LOG(ERROR) << "Failed to parse JSON config"; + return sensors_parsed; + } + + Json::Value sensors = root["Sensors"]; + std::size_t total_parsed = 0; + std::set sensors_name_parsed; + + for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) { + const std::string &name = sensors[i]["Name"].asString(); + LOG(INFO) << "Sensor[" << i << "]'s Name: " << name; + if (name.empty()) { + LOG(ERROR) << "Failed to read " + << "Sensor[" << i << "]'s Name"; + sensors_parsed.clear(); + return sensors_parsed; + } + + auto result = sensors_name_parsed.insert(name); + if (!result.second) { + LOG(ERROR) << "Duplicate Sensor[" << i << "]'s Name"; + sensors_parsed.clear(); + return sensors_parsed; + } + + std::string sensor_type_str = sensors[i]["Type"].asString(); + LOG(INFO) << "Sensor[" << name << "]'s Type: " << sensor_type_str; + TemperatureType_2_0 sensor_type; + + if (!getTypeFromString(sensor_type_str, &sensor_type)) { + LOG(ERROR) << "Invalid " + << "Sensor[" << name << "]'s Type: " << sensor_type_str; + sensors_parsed.clear(); + return sensors_parsed; + } + + std::array hot_thresholds; + hot_thresholds.fill(NAN); + std::array cold_thresholds; + cold_thresholds.fill(NAN); + std::array hot_hysteresis; + hot_hysteresis.fill(0.0); + std::array cold_hysteresis; + cold_hysteresis.fill(0.0); + + Json::Value values = sensors[i]["HotThreshold"]; + if (values.size() != kThrottlingSeverityCount) { + LOG(ERROR) << "Invalid " + << "Sensor[" << name << "]'s HotThreshold count" << values.size(); + sensors_parsed.clear(); + return sensors_parsed; + } else { + float min = std::numeric_limits::min(); + for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) { + hot_thresholds[j] = getFloatFromValue(values[j]); + if (!std::isnan(hot_thresholds[j])) { + if (hot_thresholds[j] < min) { + LOG(ERROR) << "Invalid " + << "Sensor[" << name << "]'s HotThreshold[j" << j + << "]: " << hot_thresholds[j] << " < " << min; + sensors_parsed.clear(); + return sensors_parsed; + } + min = hot_thresholds[j]; + } + LOG(INFO) << "Sensor[" << name << "]'s HotThreshold[" << j + << "]: " << hot_thresholds[j]; + } + } + + values = sensors[i]["HotHysteresis"]; + if (values.size() != kThrottlingSeverityCount) { + LOG(INFO) << "Cannot find valid " + << "Sensor[" << name << "]'s HotHysteresis, default all to 0.0"; + } 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 sensors_parsed; + } + LOG(INFO) << "Sensor[" << name << "]'s HotHysteresis[" << j + << "]: " << hot_hysteresis[j]; + } + } + + values = sensors[i]["ColdThreshold"]; + if (values.size() != kThrottlingSeverityCount) { + LOG(INFO) << "Cannot find valid " + << "Sensor[" << name << "]'s ColdThreshold, default all to NAN"; + } else { + float max = std::numeric_limits::max(); + for (Json::Value::ArrayIndex j = 0; j < kThrottlingSeverityCount; ++j) { + cold_thresholds[j] = getFloatFromValue(values[j]); + if (!std::isnan(cold_thresholds[j])) { + if (cold_thresholds[j] > max) { + LOG(ERROR) << "Invalid " + << "Sensor[" << name << "]'s ColdThreshold[j" << j + << "]: " << cold_thresholds[j] << " > " << max; + sensors_parsed.clear(); + return sensors_parsed; + } + max = cold_thresholds[j]; + } + LOG(INFO) << "Sensor[" << name << "]'s ColdThreshold[" << j + << "]: " << cold_thresholds[j]; + } + } + + values = sensors[i]["ColdHysteresis"]; + if (values.size() != kThrottlingSeverityCount) { + LOG(INFO) << "Cannot find valid " + << "Sensor[" << name << "]'s ColdHysteresis, default all to 0.0"; + } 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 sensors_parsed; + } + LOG(INFO) << "Sensor[" << name << "]'s ColdHysteresis[" << j + << "]: " << cold_hysteresis[j]; + } + } + + float vr_threshold = NAN; + 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; + + bool is_monitor = false; + if (sensors[i]["Monitor"].empty() || !sensors[i]["Monitor"].isBool()) { + LOG(INFO) << "Failed to read Sensor[" << name << "]'s Monitor, set to 'false'"; + } else { + is_monitor = sensors[i]["Monitor"].asBool(); + } + LOG(INFO) << "Sensor[" << name << "]'s Monitor: " << std::boolalpha << is_monitor + << std::noboolalpha; + + sensors_parsed[name] = { + .type = sensor_type, + .hot_thresholds = hot_thresholds, + .cold_thresholds = cold_thresholds, + .hot_hysteresis = hot_hysteresis, + .cold_hysteresis = cold_hysteresis, + .vr_threshold = vr_threshold, + .multiplier = multiplier, + .is_monitor = is_monitor, + }; + ++total_parsed; + } + + LOG(INFO) << total_parsed << " Sensors parsed successfully"; + return sensors_parsed; +} + +std::map ParseCoolingDevice(std::string_view config_path) { + std::string json_doc; + std::map cooling_devices_parsed; + if (!android::base::ReadFileToString(config_path.data(), &json_doc)) { + LOG(ERROR) << "Failed to read JSON config from " << config_path; + return cooling_devices_parsed; + } + + Json::Value root; + Json::Reader reader; + + if (!reader.parse(json_doc, root)) { + LOG(ERROR) << "Failed to parse JSON config"; + return cooling_devices_parsed; + } + + Json::Value cooling_devices = root["CoolingDevices"]; + std::size_t total_parsed = 0; + std::set cooling_devices_name_parsed; + + for (Json::Value::ArrayIndex i = 0; i < cooling_devices.size(); ++i) { + const std::string &name = cooling_devices[i]["Name"].asString(); + LOG(INFO) << "CoolingDevice[" << i << "]'s Name: " << name; + if (name.empty()) { + LOG(ERROR) << "Failed to read " + << "CoolingDevice[" << i << "]'s Name"; + cooling_devices_parsed.clear(); + return cooling_devices_parsed; + } + + auto result = cooling_devices_name_parsed.insert(name.data()); + if (!result.second) { + LOG(ERROR) << "Duplicate CoolingDevice[" << i << "]'s Name"; + cooling_devices_parsed.clear(); + return cooling_devices_parsed; + } + + 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 cooling_devices_parsed; + } + + cooling_devices_parsed[name] = cooling_device_type; + + ++total_parsed; + } + + LOG(INFO) << total_parsed << " CoolingDevices parsed successfully"; + return cooling_devices_parsed; +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/hidl/thermal/utils/config_parser.h b/hidl/thermal/utils/config_parser.h new file mode 100644 index 00000000..fe43594f --- /dev/null +++ b/hidl/thermal/utils/config_parser.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef THERMAL_UTILS_CONFIG_PARSER_H__ +#define THERMAL_UTILS_CONFIG_PARSER_H__ + +#include +#include + +#include + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::hardware::hidl_enum_range; +using ::android::hardware::thermal::V2_0::CoolingType; +using TemperatureType_2_0 = ::android::hardware::thermal::V2_0::TemperatureType; +using ::android::hardware::thermal::V2_0::ThrottlingSeverity; +constexpr size_t kThrottlingSeverityCount = std::distance( + hidl_enum_range().begin(), hidl_enum_range().end()); +using ThrottlingArray = std::array(kThrottlingSeverityCount)>; + +struct SensorInfo { + TemperatureType_2_0 type; + ThrottlingArray hot_thresholds; + ThrottlingArray cold_thresholds; + ThrottlingArray hot_hysteresis; + ThrottlingArray cold_hysteresis; + float vr_threshold; + float multiplier; + bool is_monitor; +}; + +std::map ParseSensorInfo(std::string_view config_path); +std::map ParseCoolingDevice(std::string_view config_path); + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif // THERMAL_UTILS_CONFIG_PARSER_H__ diff --git a/hidl/thermal/utils/config_schema.json b/hidl/thermal/utils/config_schema.json new file mode 100644 index 00000000..fd493e14 --- /dev/null +++ b/hidl/thermal/utils/config_schema.json @@ -0,0 +1,219 @@ +{ + "definitions":{ + + }, + "$schema":"http://json-schema.org/draft-07/schema#", + "$id":"http://example.com/root.json", + "type":"object", + "title":"The Root Schema", + "required":[ + "Sensors" + ], + "properties":{ + "Sensors":{ + "$id":"#/properties/Sensors", + "type":"array", + "title":"The Sensors Schema", + "items":{ + "$id":"#/properties/Sensors/items", + "type":"object", + "title":"The Items Schema", + "required":[ + "Name", + "Type", + "HotThreshold", + "VrThreshold", + "Multiplier" + ], + "properties":{ + "Name":{ + "$id":"#/properties/Sensors/items/properties/Name", + "type":"string", + "title":"The Name Schema", + "default":"", + "examples":[ + "cpu0-silver-usr" + ], + "pattern":"^(.+)$" + }, + "Type":{ + "$id":"#/properties/Sensors/items/properties/Type", + "type":"string", + "title":"The Type Schema", + "default":"", + "examples":[ + "CPU" + ], + "pattern":"^(.+)$" + }, + "HotThreshold":{ + "$id":"#/properties/Sensors/items/properties/HotThreshold", + "type":"array", + "title":"The hot threshold Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN", + "default":"NAN", + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/HotThreshold/items", + "type":[ + "string", + "number" + ], + "title":"The Items Schema", + "default":"", + "examples":[ + "NAN", + "NAN", + "NAN", + 95, + "NAN", + "NAN", + 125 + ], + "pattern":"^([-+]?[0-9]*\\.?[0-9]+|NAN)$" + } + }, + "HotHysteresis":{ + "$id":"#/properties/Sensors/items/properties/HotHysteresis", + "type":"array", + "title":"The hot hysteresis Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN. Throttling status will be cleared HotThreshold - HotHysteresis.", + "default":null, + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/HotHysteresis/items", + "type":[ + "number" + ], + "title":"The Items Schema", + "default":0.0, + "examples":[ + 0.0, + 0.0, + 0.0, + 1.0, + 1.5, + 1.0, + 2.0 + ] + } + }, + "ColdThreshold":{ + "$id":"#/properties/Sensors/items/properties/ColdThreshold", + "type":"array", + "title":"The cold threshold Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN, default to NAN", + "default":null, + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/ColdThreshold/items", + "type":"string", + "title":"The Items Schema", + "default":"NAN", + "examples":[ + "NAN", + "NAN", + "NAN", + "NAN", + "NAN", + "NAN", + "NAN" + ], + "pattern":"^([-+]?[0-9]*\\.?[0-9]+|NAN)$" + } + }, + "ColdHysteresis":{ + "$id":"#/properties/Sensors/items/properties/ColdHysteresis", + "type":"array", + "title":"The cold hysteresis Schema, values are thresholds from ThrottlingSeverity::NONE to ThrottlingSeverity::SHUTDOWN. Throttling status will be cleared ColdThreshold + ColdHysteresis.", + "default":null, + "maxItems":7, + "minItems":7, + "items":{ + "$id":"#/properties/Sensors/items/properties/ColdHysteresis/items", + "type":[ + "number" + ], + "title":"The Items Schema", + "default":0.0, + "examples":[ + 0.0, + 0.0, + 0.0, + 1.0, + 1.5, + 1.0, + 2.0 + ] + } + }, + "VrThreshold":{ + "$id":"#/properties/Sensors/items/properties/VrThreshold", + "type":"string", + "title":"The Vrthreshold Schema", + "default":"", + "examples":[ + "NAN" + ], + "pattern":"^(.*)$" + }, + "Multiplier":{ + "$id":"#/properties/Sensors/items/properties/Multiplier", + "type":"number", + "title":"The Multiplier Schema", + "default":0.001, + "examples":[ + 0.001 + ], + "exclusiveMinimum":0.0 + }, + "Monitor":{ + "$id":"#/properties/Sensors/items/properties/Monitor", + "type":"boolean", + "title":"The Monitor Schema, if the sensor will be monitored and used to trigger throttling event", + "default":false, + "examples":[ + true + ] + } + } + } + }, + "CoolingDevices":{ + "$id":"#/properties/CoolingDevices", + "type":"array", + "title":"The Coolingdevices Schema", + "items":{ + "$id":"#/properties/CoolingDevices/items", + "type":"object", + "title":"The Items Schema", + "required":[ + "Name", + "Type" + ], + "properties":{ + "Name":{ + "$id":"#/properties/CoolingDevices/items/properties/Name", + "type":"string", + "title":"The Name Schema", + "default":"", + "examples":[ + "thermal-cpufreq-0" + ], + "pattern":"^(.+)$" + }, + "Type":{ + "$id":"#/properties/CoolingDevices/items/properties/Type", + "type":"string", + "title":"The Type Schema", + "default":"", + "examples":[ + "CPU" + ], + "pattern":"^(.+)$" + } + } + } + } + } +} diff --git a/hidl/thermal/utils/thermal_files.cpp b/hidl/thermal/utils/thermal_files.cpp new file mode 100644 index 00000000..c3948165 --- /dev/null +++ b/hidl/thermal/utils/thermal_files.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include "thermal_files.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +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 = ""; + if (file_path.empty()) { + 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; +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/hidl/thermal/utils/thermal_files.h b/hidl/thermal/utils/thermal_files.h new file mode 100644 index 00000000..d2c86dd5 --- /dev/null +++ b/hidl/thermal/utils/thermal_files.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef THERMAL_UTILS_THERMAL_FILES_H_ +#define THERMAL_UTILS_THERMAL_FILES_H_ + +#include +#include + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +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; + size_t getNumThermalFiles() const { return thermal_name_to_path_map_.size(); } + + private: + std::unordered_map thermal_name_to_path_map_; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif // THERMAL_UTILS_THERMAL_FILES_H_ diff --git a/hidl/thermal/utils/thermal_watcher.cpp b/hidl/thermal/utils/thermal_watcher.cpp new file mode 100644 index 00000000..19e76280 --- /dev/null +++ b/hidl/thermal/utils/thermal_watcher.cpp @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "thermal_watcher.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using std::chrono_literals::operator""ms; + +void ThermalWatcher::registerFilesToWatch(const std::set &sensors_to_watch, + const std::set &cdev_to_watch, + bool uevent_monitor) { + int flags = O_RDONLY | O_CLOEXEC | O_BINARY; + + for (const auto &path : cdev_to_watch) { + android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), flags))); + if (fd == -1) { + PLOG(ERROR) << "failed to watch: " << path; + continue; + } + watch_to_file_path_map_.emplace(fd.get(), path); + fds_.emplace_back(std::move(fd)); + looper_->addFd(fd.get(), 0, Looper::EVENT_INPUT, nullptr, nullptr); + } + monitored_sensors_.insert(sensors_to_watch.begin(), sensors_to_watch.end()); + if (!uevent_monitor) { + is_polling_ = true; + return; + } + uevent_fd_.reset((TEMP_FAILURE_RETRY(uevent_open_socket(64 * 1024, true)))); + if (uevent_fd_.get() < 0) { + LOG(ERROR) << "failed to open uevent socket"; + is_polling_ = true; + return; + } + + fcntl(uevent_fd_, F_SETFL, O_NONBLOCK); + + looper_->addFd(uevent_fd_.get(), 0, Looper::EVENT_INPUT, nullptr, nullptr); + is_polling_ = false; + thermal_triggered_ = true; +} + +bool ThermalWatcher::startWatchingDeviceFiles() { + if (cb_) { + auto ret = this->run("FileWatcherThread", PRIORITY_HIGHEST); + if (ret != NO_ERROR) { + LOG(ERROR) << "ThermalWatcherThread start fail"; + return false; + } else { + LOG(INFO) << "ThermalWatcherThread started"; + return true; + } + } + return false; +} +void ThermalWatcher::parseUevent(std::set *sensors_set) { + bool thermal_event = false; + constexpr int kUeventMsgLen = 2048; + char msg[kUeventMsgLen + 2]; + char *cp; + + while (true) { + int n = uevent_kernel_multicast_recv(uevent_fd_.get(), msg, kUeventMsgLen); + if (n <= 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG(ERROR) << "Error reading from Uevent Fd"; + } + break; + } + + if (n >= kUeventMsgLen) { + LOG(ERROR) << "Uevent overflowed buffer, discarding"; + continue; + } + + msg[n] = '\0'; + msg[n + 1] = '\0'; + + cp = msg; + while (*cp) { + std::string uevent = cp; + if (!thermal_event) { + if (uevent.find("SUBSYSTEM=") == 0) { + if (uevent.find("SUBSYSTEM=thermal") != 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 (std::find(monitored_sensors_.begin(), monitored_sensors_.end(), name) != + monitored_sensors_.end()) { + sensors_set->insert(name); + } + break; + } + } + while (*cp++) { + } + } + } +} + +void ThermalWatcher::wake() { + looper_->wake(); +} + +bool ThermalWatcher::threadLoop() { + LOG(VERBOSE) << "ThermalWatcher polling..."; + // Polling interval 2s + static constexpr int kMinPollIntervalMs = 2000; + // Max uevent timeout 5mins + static constexpr int kUeventPollTimeoutMs = 300000; + int fd; + std::set sensors; + + int timeout = (thermal_triggered_ || is_polling_) ? kMinPollIntervalMs : kUeventPollTimeoutMs; + if (looper_->pollOnce(timeout, &fd, nullptr, nullptr) >= 0) { + if (fd != uevent_fd_.get()) { + return true; + } + parseUevent(&sensors); + // Ignore cb_ if uevent is not from monitored sensors + if (sensors.size() == 0) { + return true; + } + } + thermal_triggered_ = cb_(sensors); + return true; +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/hidl/thermal/utils/thermal_watcher.h b/hidl/thermal/utils/thermal_watcher.h new file mode 100644 index 00000000..45c2a737 --- /dev/null +++ b/hidl/thermal/utils/thermal_watcher.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2018 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. + */ +#ifndef THERMAL_UTILS_THERMAL_WATCHER_H_ +#define THERMAL_UTILS_THERMAL_WATCHER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using android::base::unique_fd; +using WatcherCallback = std::function &name)>; + +// A helper class for monitoring thermal files changes. +class ThermalWatcher : public ::android::Thread { + public: + ThermalWatcher(const WatcherCallback &cb) + : Thread(false), cb_(cb), looper_(new 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. + void registerFilesToWatch(const std::set &sensors_to_watch, + const std::set &cdev_to_watch, bool uevent_monitor); + // Wake up the looper thus the worker thread, immediately. This can be called + // in any thread. + void wake(); + + private: + // The work done by the watcher thread. This will use inotify to check for + // modifications to the files to watch. If any modification is seen this + // will callback the registered function with the new data read from the + // modified file. + bool threadLoop() override; + + // Parse uevent message + void parseUevent(std::set *sensor_name); + + // Maps watcher filer descriptor to watched file path. + std::unordered_map watch_to_file_path_map_; + std::vector fds_; + + // 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_; + + sp looper_; + + // For uevent socket registration. + android::base::unique_fd uevent_fd_; + // Sensor list which monitor flag is enabled. + std::set monitored_sensors_; + // Flag to point out if any sensor across the first threshold. + bool thermal_triggered_; + // Flag to point out if device can support uevent notify. + bool is_polling_; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif // THERMAL_UTILS_THERMAL_WATCHER_H_