From a6ffd076d05fe504f15bebab848a4e0eca354109 Mon Sep 17 00:00:00 2001 From: Daniel Lezcano Date: Fri, 10 Sep 2021 14:03:44 +0200 Subject: Initial commit Signed-off-by: Daniel Lezcano --- Android.bp | 49 ++ Config.cpp | 330 +++++++++++++ Config.h | 102 ++++ CpuInfo.cpp | 113 +++++ CpuInfo.h | 45 ++ LibThermal.c | 32 ++ LibThermal.cpp | 111 +++++ LibThermal.h | 84 ++++ Thermal.cpp | 566 +++++++++++++++++++++++ Thermal.h | 124 +++++ android.hardware.thermal@2.0-service-generic.rc | 6 + android.hardware.thermal@2.0-service-generic.xml | 12 + service.cpp | 63 +++ 13 files changed, 1637 insertions(+) create mode 100644 Android.bp create mode 100644 Config.cpp create mode 100644 Config.h create mode 100644 CpuInfo.cpp create mode 100644 CpuInfo.h create mode 100644 LibThermal.c create mode 100644 LibThermal.cpp create mode 100644 LibThermal.h create mode 100644 Thermal.cpp create mode 100644 Thermal.h create mode 100644 android.hardware.thermal@2.0-service-generic.rc create mode 100644 android.hardware.thermal@2.0-service-generic.xml create mode 100644 service.cpp diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..c6230c0 --- /dev/null +++ b/Android.bp @@ -0,0 +1,49 @@ +// +// 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_binary { + name: "android.hardware.thermal@2.0-service.generic", + defaults: ["hidl_defaults"], + relative_install_path: "hw", + vendor: true, + init_rc: ["android.hardware.thermal@2.0-service-generic.rc"], + vintf_fragments: ["android.hardware.thermal@2.0-service-generic.xml"], + srcs: [ + "Thermal.cpp", + "Config.cpp", + "CpuInfo.cpp", + "LibThermal.cpp", + "service.cpp" + ], + include_dirs: ["external/libthermal/include"], + shared_libs: [ + "libthermal", + "libbase", + "libhidlbase", + "libutils", + "libjsoncpp", + "android.hardware.thermal@2.0", + "android.hardware.thermal@1.0", + ], +} diff --git a/Config.cpp b/Config.cpp new file mode 100644 index 0000000..31f87b7 --- /dev/null +++ b/Config.cpp @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2021 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 "Config.h" + +#include +#include +#include + +#include +#include + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +template bool Config::typeToEnum(std::string &type, T &t) +{ + if (type.empty()) + return false;; + + for (const auto cdt : hidl_enum_range()) { + + const std::string upperType = toString(cdt); + + /* + * Let's compare on uppercase only so we can be + * comfortable with the syntax used in the + * configuration file. + */ + if (toUpper(type) != toUpper(upperType)) + continue; + + t = cdt; + + return true; + } + + return false; +} + +std::string Config::toUpper(const std::string &str) +{ + std::string upper_str(str); + + for (std::string::size_type i = 0; i < upper_str.length(); i++) + upper_str[i] = std::toupper(str[i], std::locale()); + + return upper_str; +} + +bool Config::readHotColdThrottling(Json::Value &throttling, + float *tempThreshold) +{ + if (throttling.empty()) + return false; + + for (const std::string mn : throttling.getMemberNames()) { + + for (const auto ts : hidl_enum_range()) { + + const std::string severity = toString(ts); + + if (toUpper(severity) != toUpper(mn)) + continue; + + tempThreshold[(int)ts] = throttling[mn].asFloat(); + + LOG(INFO) << "Throttling[\"" << severity << "\"] = " << + tempThreshold[(int)ts] << "°C"; + + } + } + + return true; +} + +bool Config::readColdThrottling(Json::Value &throttling, + TemperatureThreshold &tempThreshold) +{ + return readHotColdThrottling(throttling, + tempThreshold.coldThrottlingThresholds.data()); +} + +bool Config::readHotThrottling(Json::Value &throttling, + TemperatureThreshold &tempThreshold) +{ + return readHotColdThrottling(throttling, + tempThreshold.hotThrottlingThresholds.data()); +} + +bool Config::readVrThrottling(Json::Value &throttling, + TemperatureThreshold &tempThreshold) +{ + LOG(INFO) << "Reading Virtual Reality configuration"; + + if (throttling["None"].empty()) { + LOG(ERROR) << "Invalid temperature threshold"; + return false; + } + + tempThreshold.vrThrottlingThreshold = throttling["None"].asFloat(); + + LOG(INFO) << "Virtual Reality Throtlling: " << + tempThreshold.vrThrottlingThreshold << "°C"; + + return true; +} + +void Config::initThreshold(TemperatureThreshold &tempThreshold) +{ + for (int i = 0; i < tempThreshold.hotThrottlingThresholds.size(); i++) + tempThreshold.hotThrottlingThresholds[i] = NAN; + + for (int i = 0; i < tempThreshold.coldThrottlingThresholds.size(); i++) + tempThreshold.coldThrottlingThresholds[i] = NAN; + + tempThreshold.vrThrottlingThreshold = NAN; +} + +bool Config::readThrottling(const std::string &name, Json::Value &throttling) +{ + TemperatureThreshold tempThreshold = { + .name = name, + }; + + initThreshold(tempThreshold); + + if (throttling.empty()) { + LOG(INFO) << "No threshold specified for '" << name << "'"; + return true; + } + + for (Json::Value::ArrayIndex i = 0; i < throttling.size(); ++i) { + + std::string type = throttling[i]["Type"].asString(); + + LOG(INFO) << "Getting '" << type << "' throttling configuration"; + + if (type == "Hot") { + if (!readHotThrottling(throttling[i], tempThreshold)) { + LOG(ERROR) << "Failed to read hot throttling entry"; + return false; + } + } else if (type == "Cold") { + if (!readColdThrottling(throttling[i], tempThreshold)) { + LOG(ERROR) << "Failed to read cold throttling entry"; + return false; + } + } else if (type == "Vr") { + if (!readVrThrottling(throttling[i], tempThreshold)) { + LOG(ERROR) << "Failed to read Virtual Reality throttling entry"; + return false; + } + } else { + LOG(ERROR) << "Invalid Throttling type: " << type; + return false; + } + + tempThreshold.name = name; + } + + this->m_threshold.insert(std::pair(name, tempThreshold)); + + return true; +} + +bool Config::readSensor(Json::Value &sensor) +{ + std::string name = sensor["Name"].asString(); + std::string type = sensor["Type"].asString(); + + Temperature_1_0 temperature_1_0; + Temperature_2_0 temperature_2_0; + + if (name.empty()) { + LOG(ERROR) << "Missing sensor name section"; + return false; + } + + temperature_1_0.name = name; + temperature_2_0.name = name; + + if (typeToEnum(type, temperature_1_0.type)) + m_temperature_1_0.push_back(temperature_1_0); + + if (typeToEnum(type, temperature_2_0.type)) + m_temperature_2_0.push_back(temperature_2_0); + + /* + * The skin temperature sensors are special ones and are + * stored in a second list for quick access for monitoring + */ + if (temperature_1_0.type == V1_0::TemperatureType::SKIN) + m_skin_sensors.push_back(name); + + LOG(INFO) << "Sensor: '" << name << "' / type: " << type; + + if (!readThrottling(name, sensor["Throttling"])) + return false; + + return true; +} + +bool Config::readCoolingDevice(Json::Value &coolingDevice) +{ + std::string name = coolingDevice["Name"].asString(); + std::string type = coolingDevice["Type"].asString(); + + CoolingDevice_1_0 coolingDevice_1_0; + CoolingDevice_2_0 coolingDevice_2_0; + + if (name.empty() || type.empty()) { + LOG(ERROR) << "Missing Cooling device name/type"; + return false; + } + + coolingDevice_1_0.name = name; + coolingDevice_2_0.name = name; + + if (typeToEnum(type, coolingDevice_1_0.type)) + m_cooling_device_1_0.push_back(coolingDevice_1_0); + + if (typeToEnum(type, coolingDevice_2_0.type)) + m_cooling_device_2_0.push_back(coolingDevice_2_0); + + return true; +} + +bool Config::readFile(const std::string path, std::stringstream &config) +{ + std::ifstream file; + + LOG(INFO) << "Reading configuration file: " << path; + + file.open(path); + if (!file) { + LOG(ERROR) << "Failed to open: " << path; + return false; + } + + config << file.rdbuf(); + + LOG(INFO) << config.str(); + + file.close(); + + return true; +} + +bool Config::parseFile(std::string path, Json::Value &root) +{ + std::stringstream config; + std::string strerr; + + Json::CharReaderBuilder builder; + std::unique_ptr reader(builder.newCharReader()); + + if (!readFile(path, config)) { + LOG(ERROR) << "Failed to read configuration file"; + return false; + } + + if (!reader->parse(&*config.str().begin(), &*config.str().end(), &root, &strerr)) { + LOG(ERROR) << "Failed to parse JSON config: " << strerr; + return false; + } + + LOG(INFO) << "Configuration file parsed successfully"; + + return true; +} + +bool Config::read(std::string path) +{ + Json::Value root; + Json::Value sensors; + Json::Value coolingDevices; + + if (!parseFile(path, root)) + return false; + + sensors = root["Sensors"]; + + for (Json::Value::ArrayIndex i = 0; i < sensors.size(); ++i) { + + if (!readSensor(sensors[i])) + return false; + } + + coolingDevices = root["CoolingDevices"]; + + for (Json::Value::ArrayIndex i = 0; i < coolingDevices.size(); ++i) { + + if (!readCoolingDevice(coolingDevices[i])) + return false; + } + + return true; +} + +bool Config::init(void) +{ + std::string property("vendor.thermal.config"); + std::string default_conf("thermal.json"); + std::string path = "/vendor/etc/" + android::base::GetProperty(property, default_conf); + + return read(path); +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/Config.h b/Config.h new file mode 100644 index 0000000..f4a2fa5 --- /dev/null +++ b/Config.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2021 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_CONFIG_PARSER_H__ +#define THERMAL_CONFIG_PARSER_H__ + +#include + +#include +#include + +#include +#include + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using Temperature_1_0 = ::android::hardware::thermal::V1_0::Temperature; +using Temperature_2_0 = ::android::hardware::thermal::V2_0::Temperature; +using CoolingDevice_1_0 = ::android::hardware::thermal::V1_0::CoolingDevice; +using CoolingDevice_2_0 = ::android::hardware::thermal::V2_0::CoolingDevice; + +using ::android::hardware::thermal::V2_0::TemperatureThreshold; +using ::android::hardware::thermal::V2_0::TemperatureType; + +class Config { +private: + template bool typeToEnum(std::string &type, T &t); + + std::string toUpper(const std::string &str); + + bool read(std::string file); + + void initThreshold(TemperatureThreshold &tempThreshold); + + bool readHotColdThrottling(Json::Value &throttling, float *tempThreshold); + + bool readHotThrottling(Json::Value &throttling, + TemperatureThreshold &tempThreshold); + + bool readColdThrottling(Json::Value &throttling, + TemperatureThreshold &tempThreshold); + + bool readVrThrottling(Json::Value &throttling, + TemperatureThreshold &tempThreshold); + + bool readThrottling(const std::string &name, + Json::Value &throttling); + + bool readCoolingDevice(Json::Value &coolingDevice); + + bool readSensor(Json::Value &sensor); + + bool readFile(const std::string path, + std::stringstream &config); + + bool parseFile(std::string path, Json::Value &root); + +public: + std::vector m_temperature_1_0; + std::vector m_temperature_2_0; + + std::vector m_cooling_device_1_0; + std::vector m_cooling_device_2_0; + + /* + * Each sensor can have associated a configuration for the + * threshold, the key is the name of the sensor. + */ + std::map m_threshold; + + /* + * Contains the list of the skin temperature sensors. + */ + std::vector m_skin_sensors; + + bool init(void); +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif diff --git a/CpuInfo.cpp b/CpuInfo.cpp new file mode 100644 index 0000000..88eeecb --- /dev/null +++ b/CpuInfo.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 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 "CpuInfo.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +#define CPU_USAGE "/proc/stat" + +bool CpuInfo::parseCpuUsagesFileAndAssignUsages(hidl_vec &cpuUsages) +{ + std::string line; + std::ifstream file; + std::regex regEx("^cpu[0-9](.+)"); + + file.open(CPU_USAGE); + if (!file) { + LOG(ERROR) << "Failed to open: " << CPU_USAGE; + return false; + } + + while (std::getline(file, line)) { + + uint64_t cpuNum, cpuUser, cpuNice, cpuSys, cpuIdle; + std::vector fields; + + if (!regex_match(line, regEx)) + continue; + + fields = android::base::Split(line, " "); + + /* + * The /proc/stat file is an ABI and not supposed to + * change. We can trust its content and the + * format. There is no need to check if the CPU is + * online as it will be removed from the file if it is + * offline. Consequently, if we can read the usage for + * a particular CPU, that means it is in the list, + * thus online. + */ + cpuNum = std::stoi(fields[0].substr(3)); + cpuUser = std::stoi(fields[1]); + cpuNice = std::stoi(fields[2]); + cpuSys = std::stoi(fields[3]); + cpuIdle = std::stoi(fields[4]); + + cpuUsages[cpuNum].name = fields[0]; + cpuUsages[cpuNum].active = cpuUser + cpuNice + cpuSys; + cpuUsages[cpuNum].total = cpuUser + cpuNice + cpuSys + cpuIdle; + cpuUsages[cpuNum].isOnline = true; + } + + file.close(); + + return true; +} + +bool CpuInfo::CpuUsages(hidl_vec &cpuUsages) +{ + if (!m_NrCpus) + return false; + + /* + * Set the vector size to the number of possible CPUs + */ + cpuUsages.resize(m_NrCpus); + + /* + * Parse the stat file + */ + parseCpuUsagesFileAndAssignUsages(cpuUsages); + + return true; +} + +CpuInfo::CpuInfo() +{ + m_NrCpus = sysconf(_SC_NPROCESSORS_CONF); +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/CpuInfo.h b/CpuInfo.h new file mode 100644 index 0000000..720f948 --- /dev/null +++ b/CpuInfo.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 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_CPUINFO_H +#define ANDROID_HARDWARE_THERMAL_V2_0_CPUINFO_H + +#include + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::hardware::hidl_vec; +using ::android::hardware::thermal::V1_0::CpuUsage; + +class CpuInfo { +private: + std::size_t m_NrCpus; + bool parseCpuUsagesFileAndAssignUsages(hidl_vec &cpuUsages); +public: + bool CpuUsages(hidl_vec &cpuUsages); + CpuInfo(); +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android +#endif diff --git a/LibThermal.c b/LibThermal.c new file mode 100644 index 0000000..bfe6c7d --- /dev/null +++ b/LibThermal.c @@ -0,0 +1,32 @@ +/* + * 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. + */ + +int Thermal::show_tz(struct thermal_zone *tz, void *arg) +{ + LOG(INFO) << "thermal zone '" << tz->name << "' " + << "id=" << tz->id; + + return arg == NULL; +} + +LibThermal::LibThermal() +{ + m_th = thermal_init(&m_ops); + + m_tz = thermal_zone_discover(m_th); + + for_each_thermal_zone(tz, show_tz, m_th); +} diff --git a/LibThermal.cpp b/LibThermal.cpp new file mode 100644 index 0000000..5384307 --- /dev/null +++ b/LibThermal.cpp @@ -0,0 +1,111 @@ +/* + * 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 "LibThermal.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + + +struct thermal_zone *LibThermal::getThermalZone(int id) +{ + return thermal_zone_find_by_id(this->m_tz, id); +} + +struct thermal_zone *LibThermal::getThermalZone(std::string name) +{ + return thermal_zone_find_by_name(this->m_tz, name.c_str()); +} + +int LibThermal::getThermalZonetemp(struct thermal_zone *tz) +{ + if (thermal_cmd_get_temp(this->m_th, tz)) { + LOG(ERROR) << "Failed to read '" << tz->name << "' temperature"; + return INT_MAX; + } + + return tz->temp; +} + +int LibThermal::getThermalZonetemp(int id) +{ + struct thermal_zone *tz; + + tz = getThermalZone(id); + if (!tz) { + LOG(ERROR) << "Thermal zone not found"; + return INT_MAX; + } + + return getThermalZonetemp(tz); +} + +int LibThermal::getThermalZonetemp(const std::string name) +{ + struct thermal_zone *tz; + + tz = getThermalZone(name); + if (!tz) { + LOG(ERROR) << "Thermal zone <" << name << "> not found"; + return INT_MAX; + } + + return getThermalZonetemp(tz); +} + +bool LibThermal::init(LibThermalCallbacks &cb) +{ + m_ops.events.tz_create = cb.thermalZoneCreate; + m_ops.events.tz_delete = cb.thermalZoneDelete; + m_ops.events.tz_disable = cb.thermalZoneDisable; + m_ops.events.tz_enable = cb.thermalZoneEnable; + m_ops.events.trip_high = cb.tripHigh; + m_ops.events.trip_low = cb.tripLow; + m_ops.events.trip_add = cb.tripAdd; + m_ops.events.trip_delete = cb.tripDelete; + m_ops.events.trip_change = cb.tripChange; + m_ops.events.cdev_add = cb.cdevAdd; + m_ops.events.cdev_delete = cb.cdevDelete; + m_ops.events.cdev_update = cb.cdevUpdate; + m_ops.events.gov_change = cb.govChange; + + m_ops.sampling.tz_temp = cb.thermalZoneTemp; + + m_th = thermal_init(&m_ops); + if (!m_th) { + LOG(ERROR) << "Failed to initialize the thermal library"; + return false; + } + + m_tz = thermal_zone_discover(m_th); + if (!m_tz) { + LOG(ERROR) << "Failed to discover the thermal zones"; + return false; + } + + return true; +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/LibThermal.h b/LibThermal.h new file mode 100644 index 0000000..809c930 --- /dev/null +++ b/LibThermal.h @@ -0,0 +1,84 @@ +/* + * 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 "libthermal.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +class LibThermalCallbacks { +public: + /* + * Callbacks called when a thermal zone event occurs + */ + int (*thermalZoneCreate)(const char *, int, void *); + int (*thermalZoneDelete) (int, void *); + int (*thermalZoneEnable)(int, void *); + int (*thermalZoneDisable)(int, void *); + + /* + * Callbacks called when a trip event occurs + */ + int (*tripHigh)(int, int, int, void *); + int (*tripLow)(int, int, int, void *); + int (*tripAdd)(int, int, int, int, int, void *); + int (*tripChange)(int, int, int, int, int, void *); + int (*tripDelete)(int, int, void *); + + /* + * Callbacks called when a cooling device event occurs + */ + int (*cdevAdd)(const char *, int, int, void *); + int (*cdevDelete)(int, void *); + int (*cdevUpdate)(int, int, void *); + + /* + * Callback called when a governor event occurs + */ + int (*govChange)(int, const char *, void *); + + /* + * Callback called when there is a temperature sampling + */ + int (*thermalZoneTemp)(int, int, void *); +}; + +class LibThermal { + +private: + struct thermal_zone *getThermalZone(int id); + struct thermal_zone *getThermalZone(std::string name); + +public: + struct thermal_handler *m_th; + struct thermal_zone *m_tz; + struct thermal_ops m_ops; + + bool init(LibThermalCallbacks &); + int getThermalZonetemp(const std::string name); + int getThermalZonetemp(struct thermal_zone *tz); + int getThermalZonetemp(int id); +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/Thermal.cpp b/Thermal.cpp new file mode 100644 index 0000000..66885cc --- /dev/null +++ b/Thermal.cpp @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2021 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 LOG_TAG "android.hardware.thermal@2.0-service-generic" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "Thermal.h" + +#define MAX_EVENTS 10 +#define SKIN_POLLING_SEC 5 // Unit is in second + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::interfacesEqual; +using ::android::hardware::thermal::V1_0::ThermalStatus; +using ::android::hardware::thermal::V1_0::ThermalStatusCode; + +std::set> gCallbacks; + +Return Thermal::getCpuUsages(getCpuUsages_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + hidl_vec cpuUsages; + + if (m_cpuInfo.CpuUsages(cpuUsages)) + _hidl_cb(status, cpuUsages); + + return Void(); +} + +Return Thermal::getCoolingDevices(getCoolingDevices_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + + if (this->m_config.m_cooling_device_1_0.empty()) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "No cooling devices"; + } + + _hidl_cb(status, this->m_config.m_cooling_device_1_0); + + return Void(); +} + +Return Thermal::getCurrentCoolingDevices(bool filterType, CoolingType type, + getCurrentCoolingDevices_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + + std::vector cooling_devices; + + for (auto const& p : this->m_config.m_cooling_device_2_0) { + + if (filterType && (type != p.type)) + continue; + + cooling_devices.push_back(p); + } + + if (cooling_devices.empty()) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "No cooling device matching the type"; + } + + _hidl_cb(status, cooling_devices); + + return Void(); +} + +Return Thermal::registerThermalChangedCallback(const sp& callback, + bool filterType, TemperatureType type, + registerThermalChangedCallback_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + std::lock_guard cbLock(m_callback_mutex); + + if (callback == nullptr) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "Invalid nullptr callback"; + LOG(ERROR) << status.debugMessage; + goto out; + } + + if (std::any_of(m_callbacks.begin(), + m_callbacks.end(), + [&](const CallbackSetting& c) { + return interfacesEqual(c.callback, callback); + })) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "Same callback interface registered already"; + LOG(ERROR) << status.debugMessage; + goto out; + } + + m_callbacks.emplace_back(callback, filterType, type); + LOG(INFO) << "A callback has been registered to ThermalHAL, isFilter: " + << filterType << " Type: " << toString(type); +out: + _hidl_cb(status); + return Void(); +} + +Return Thermal::unregisterThermalChangedCallback(const sp& callback, + unregisterThermalChangedCallback_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + std::lock_guard cbLock(m_callback_mutex); + std::vector::iterator it; + + if (callback == nullptr) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "Invalid nullptr callback"; + LOG(ERROR) << status.debugMessage; + goto out; + } + + it = std::remove_if(m_callbacks.begin(), m_callbacks.end(), + [&](const CallbackSetting& c) { + return interfacesEqual(c.callback, callback); + }); + + if (it == m_callbacks.end()) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "The callback was not registered before"; + LOG(ERROR) << status.debugMessage; + goto out; + } + + LOG(INFO) << "A callback has been unregistered from ThermalHAL, isFilter: " + << (*it).is_filter_type << " Type: " << toString((*it).type); + + m_callbacks.erase(it); +out: + _hidl_cb(status); + + return Void(); +} + +void Thermal::thermalChangedCallback(const std::vector &temperature) +{ + std::lock_guard cbLock(m_callback_mutex); + + for (auto &t : temperature) { + + 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); + + for (auto &c : m_callbacks) { + + if (!c.is_filter_type || c.type == t.type) { + c.callback->notifyThrottling(t); + } + } + } +} + +int Thermal::thermalZoneCreate(const char *name, int tz_id, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Thermal zone " << name << "/" << tz_id << " created"; + + thermal = NULL; + + return 0; +} + +int Thermal::thermalZoneDelete(int tz_id, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Thermal zone " << tz_id << " delete"; + + thermal = NULL; + + return 0; +} + +int Thermal::thermalZoneEnable(int tz_id, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Thermal zone " << tz_id << " enabled"; + + thermal = NULL; + + return 0; +} + +int Thermal::thermalZoneDisable(int tz_id, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Thermal zone " << tz_id << " disabled"; + + thermal = NULL; + + return 0; +} + +int Thermal::tripHigh(int tz_id, int trip_id, int temp, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Thermal zone " << tz_id << " trip crossed the way up " + << "with trip_id=" << trip_id << ",temp=" << temp; + + temp = thermal->m_libThermal.getThermalZonetemp(tz_id); + + return 0; +} + +int Thermal::tripLow(int tz_id, int trip_id, int temp, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Thermal zone " << tz_id << " trip crossed the way down " + << "with trip_id=" << trip_id << ",temp=" << temp; + + temp = thermal->m_libThermal.getThermalZonetemp(tz_id); + + return 0; +} + +int Thermal::tripAdd(int tz_id, int trip_id, int type, + int temp, int hyst, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Trip " << trip_id << " from thermal zone " + << tz_id << " added: type=" + << type << ", temp=" << temp << " ,hyst=" << hyst; + + thermal = NULL; + + return 0; +} + +int Thermal::tripChange(int tz_id, int trip_id, int type, + int temp, int hyst, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Trip " << trip_id << " from thermal zone " + << tz_id << " changed: type=" + << type << ", temp=" << temp << " ,hyst=" << hyst; + + thermal = NULL; + + return 0; +} + +int Thermal::tripDelete(int tz_id, int trip_id, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Trip " << trip_id << " from thermal zone " + << tz_id << " deleted"; + + thermal = NULL; + + return 0; +} + +int Thermal::cdevAdd(const char *name, int cdev_id, int max_state, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Cooling device '" << name << "'/" << cdev_id + << " max state" << max_state << " created"; + + thermal = NULL; + + return 0; +} + +int Thermal::cdevDelete(int cdev_id, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Cooling device " << cdev_id << " deleted"; + + thermal = NULL; + + return 0; +} + +int Thermal::cdevUpdate(int cdev_id, int max_state, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Coolling device " << cdev_id << " max state" + << max_state; + + thermal = NULL; + + return 0; +} + +int Thermal::govChange(int tz_id, const char *name, void *arg) +{ + class Thermal *thermal = (class Thermal *)arg; + + LOG(DEBUG) << "Governor change '" << name << "' " + << "for thermal zone " << tz_id; + + thermal = NULL; + + return 0; +} + +bool Thermal::skinTemperatureMonitor(class Thermal *thermal) +{ + uint64_t expirations; + + if (read(thermal->m_timerfd, &expirations, sizeof(expirations)) < 0) { + LOG(ERROR) << "Failed to read signal data"; + return false; + } + + for (auto const& p : thermal->m_config.m_skin_sensors) { + LOG(DEBUG) << "Skin temperature for '" << p << "' " + << thermal->m_libThermal.getThermalZonetemp(p) << "°C"; + } + + return true; +} + +Return Thermal::getTemperatureThresholds(bool filterType, TemperatureType type, + getTemperatureThresholds_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + std::vector thresholds; + + for (auto const& p : this->m_config.m_threshold) { + + if (filterType && (type != p.second.type)) + continue; + + thresholds.push_back(p.second); + } + + if (thresholds.empty()) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "No threshold temperature matching the type"; + } + + _hidl_cb(status, thresholds); + + return Void(); +} + +// Methods from ::android::hardware::thermal::V2_0::IThermal follow. +Return Thermal::getCurrentTemperatures(bool filterType, TemperatureType type, + getCurrentTemperatures_cb _hidl_cb) +{ + ThermalStatus status = { .code = ThermalStatusCode::SUCCESS }; + std::vector temperatures; + + for (auto &p : this->m_config.m_temperature_2_0) { + + std::string name = p.name; + int temperature; + + if (filterType && (type != p.type)) + continue; + + temperature = m_libThermal.getThermalZonetemp(name); + if (temperature == INT_MAX) { + LOG(ERROR) << "Failed to read thermal zone temperature" + << "for <" << name << ">"; + continue; + } + + p.value = temperature / 1000; + temperatures.push_back(p); + } + + if (temperatures.empty()) { + status.code = ThermalStatusCode::FAILURE; + status.debugMessage = "No temperature matching the type"; + } + + _hidl_cb(status, temperatures); + + return Void(); +} + +// Methods from ::android::hardware::thermal::V1_0::IThermal follow. +Return Thermal::getTemperatures(getTemperatures_cb _hidl_cb) +{ + ThermalStatus status = { . code = ThermalStatusCode::SUCCESS }; + + for (auto &p : this->m_config.m_temperature_1_0) { + + std::string name = p.name; + int temperature; + + temperature = m_libThermal.getThermalZonetemp(name); + if (temperature == INT_MAX) { + LOG(ERROR) << "Failed to read thermal zone temperature" + << "for <" << name << ">"; + continue; + } + + p.currentValue = temperature / 1000; + } + + _hidl_cb(status, m_config.m_temperature_1_0); + + return Void(); +} + +bool Thermal::threadLoop() { + + struct epoll_event ev; + struct epoll_event events[MAX_EVENTS]; + int epollfd; + int nfds; + int i; + + LibThermalCallbacks cb; + + cb.thermalZoneCreate = this->thermalZoneCreate; + cb.thermalZoneDelete = this->thermalZoneDelete; + cb.thermalZoneEnable = this->thermalZoneEnable; + cb.thermalZoneDisable = this->thermalZoneDisable; + cb.tripHigh = this->tripHigh; + cb.tripLow = this->tripLow; + cb.tripAdd = this->tripAdd; + cb.tripChange = this->tripChange; + cb.tripDelete = this->tripDelete; + cb.cdevAdd = this->cdevAdd; + cb.cdevDelete = this->cdevDelete; + cb.cdevUpdate = this->cdevUpdate; + cb.govChange = this->govChange; + + if (!m_config.init()) + LOG(FATAL) << "ThermalHAL failed to initialize the configuration"; + + if (!m_libThermal.init(cb)) + LOG(FATAL) << "ThermalHAL failed to initialize the thermal library"; + + epollfd = epoll_create1(0); + if (epollfd < 0) + return false; + + ev.events = EPOLLIN; + ev.data.ptr = (void *)thermal_events_handle; + + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, + thermal_events_fd(m_libThermal.m_th), &ev) == -1) + return false; + + /* + * We want to read the temperature every 2000ms, set a timer + * file descriptor to be woken up at regular intervals. + */ + struct itimerspec iti = { + .it_interval = { SKIN_POLLING_SEC, 0 }, + .it_value = { SKIN_POLLING_SEC, 0 }, + }; + + m_timerfd = timerfd_create(CLOCK_MONOTONIC, 0); + if (m_timerfd < 0) { + LOG(ERROR) << "Failed to create timer"; + return false; + } + + if (timerfd_settime(m_timerfd, 0, &iti, NULL) < 0) { + LOG(ERROR) << "Failed to set timer time"; + return false; + } + + /* + * There are skin temperature sensors, set a timer to poll the + * temperature. + */ + if (!m_config.m_skin_sensors.empty()) { + + LOG(INFO) << "Skin sensors configured, setting up the monitoring."; + + ev.events = EPOLLIN; + ev.data.ptr = (void *)skinTemperatureMonitor; + + if (epoll_ctl(epollfd, EPOLL_CTL_ADD, m_timerfd, &ev) == -1) + return false; + } + + /* + * No ops was specified for the temperature sampling, so no + * event of this type should occur, but add the corresponding + * code to handle it for unlikely future changes if the code + * needs to have the temperature sampling. + * ev.events = EPOLLIN; + * ev.data.ptr = (void *)thermal_sampling_handle; + * + * if (epoll_ctl(epollfd, EPOLL_CTL_ADD, + * thermal_sampling_fd(m_libThermal.m_th), &ev) == -1) + * return false; + */ + + while (1) { + + nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); + + if (nfds < 0) { + if (errno == EINTR) + continue; + LOG(FATAL) << "Mainloop failed with error: " << strerror(errno); + } + + for (i = 0; i < nfds; i++) { + if (events[i].data.ptr == thermal_events_handle) { + thermal_events_handle(m_libThermal.m_th, NULL); + } else if (events[i].data.ptr == thermal_sampling_handle) { + thermal_sampling_handle(m_libThermal.m_th, NULL); + } else if (events[i].data.ptr == skinTemperatureMonitor) { + skinTemperatureMonitor(this); + } + } + } + + return true; +} + +Thermal::Thermal() +{ + this->run("ThermalMonitorThread", PRIORITY_HIGHEST); +} + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android diff --git a/Thermal.h b/Thermal.h new file mode 100644 index 0000000..0bfe6d2 --- /dev/null +++ b/Thermal.h @@ -0,0 +1,124 @@ +/* + * 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_THERMAL_H +#define ANDROID_HARDWARE_THERMAL_V2_0_THERMAL_H + +#include +#include +#include + +#include + +#include "Config.h" +#include "CpuInfo.h" +#include "LibThermal.h" + +namespace android { +namespace hardware { +namespace thermal { +namespace V2_0 { +namespace implementation { + +using ::android::sp; +using ::android::hardware::hidl_array; +using ::android::hardware::hidl_memory; +using ::android::hardware::hidl_string; +using ::android::hardware::hidl_vec; +using ::android::hardware::Return; +using ::android::hardware::Void; +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 ::android::hardware::thermal::V2_0::IThermalChangedCallback; +using ::android::hardware::thermal::V2_0::TemperatureThreshold; +using ::android::hardware::thermal::V2_0::TemperatureType; + +struct CallbackSetting { + CallbackSetting(sp callback, bool is_filter_type, TemperatureType type) + : callback(callback), is_filter_type(is_filter_type), type(type) {} + sp callback; + bool is_filter_type; + TemperatureType type; +}; + +class Thermal : public IThermal, ::android::Thread { + public: + // Methods from ::android::hardware::thermal::V1_0::IThermal follow. + 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 type, + getCurrentTemperatures_cb _hidl_cb) override; + Return getTemperatureThresholds(bool filterType, TemperatureType type, + getTemperatureThresholds_cb _hidl_cb) override; + Return registerThermalChangedCallback( + const sp& callback, bool filterType, TemperatureType 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; + + Thermal(); + +private: + static int thermalZoneDelete(int, void *); + static int thermalZoneCreate(const char *, int, void *); + static int thermalZoneEnable(int tz_id, void *arg); + static int thermalZoneDisable(int tz_id, void *arg); + static int tripHigh(int tz_id, int trip_id, int temp, void *arg); + static int tripLow(int tz_id, int trip_id, int temp, void *arg); + static int tripAdd(int tz_id, int trip_id, int type, + int temp, int hyst, void *arg); + static int tripChange(int tz_id, int trip_id, int type, + int temp, int hyst, void *arg); + static int tripDelete(int tz_id, int trip_id, void *arg); + static int cdevAdd(const char *name, int cdev_id, int max_state, void *arg); + static int cdevDelete(int cdev_id, void *arg); + static int cdevUpdate(int cdev_id, int max_state, void *arg); + static int govChange(int tz_id, const char *name, void *arg); + + static bool skinTemperatureMonitor(class Thermal *thermal); + + void thermalChangedCallback(const std::vector &temperature); + + bool threadLoop() override; + + Config m_config; + CpuInfo m_cpuInfo; + LibThermal m_libThermal; + + int m_timerfd; + + std::mutex m_callback_mutex; + std::vector m_callbacks; +}; + +} // namespace implementation +} // namespace V2_0 +} // namespace thermal +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_THERMAL_V2_0_THERMAL_H diff --git a/android.hardware.thermal@2.0-service-generic.rc b/android.hardware.thermal@2.0-service-generic.rc new file mode 100644 index 0000000..b9ba400 --- /dev/null +++ b/android.hardware.thermal@2.0-service-generic.rc @@ -0,0 +1,6 @@ +service vendor.thermal-hal-2-0-generic /vendor/bin/hw/android.hardware.thermal@2.0-service.generic + interface android.hardware.thermal@1.0::IThermal generic + interface android.hardware.thermal@2.0::IThermal generic + class hal + user system + group system diff --git a/android.hardware.thermal@2.0-service-generic.xml b/android.hardware.thermal@2.0-service-generic.xml new file mode 100644 index 0000000..9dad190 --- /dev/null +++ b/android.hardware.thermal@2.0-service-generic.xml @@ -0,0 +1,12 @@ + + + android.hardware.thermal + hwbinder + 1.0 + 2.0 + + IThermal + generic + + + diff --git a/service.cpp b/service.cpp new file mode 100644 index 0000000..62666a6 --- /dev/null +++ b/service.cpp @@ -0,0 +1,63 @@ +/* + * 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. + */ + +#define LOG_TAG "android.hardware.thermal@2.0-service-generic" + +#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) << "Thermal Service is shutting down."; + return 1; +} + +int main(int /* argc */, char** /* argv */) { + status_t status; + android::sp service = nullptr; + + LOG(INFO) << "Thermal HAL Service generic 2.0 starting..."; + + service = new Thermal(); + if (service == nullptr) { + LOG(ERROR) << "Error creating an instance of ThermalHAL. Exiting..."; + return shutdown(); + } + + configureRpcThreadpool(1, true /* callerWillJoin */); + + status = service->registerAsService("generic"); + if (status != OK) { + LOG(ERROR) << "Could not register service for ThermalHAL (" << status << ")"; + return shutdown(); + } + + LOG(INFO) << "Thermal Service started successfully."; + joinRpcThreadpool(); + // We should not get past the joinRpcThreadpool(). + return shutdown(); +} -- cgit v1.2.3