/* * 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; /* * The phone 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