| /* |
| * Copyright (c) 2020, 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. |
| */ |
| |
| #include <cstdio> |
| #include <cinttypes> |
| #include <string> |
| #include <dirent.h> |
| #include <unordered_map> |
| #include <fstream> |
| |
| #include <android-base/logging.h> |
| #include "thermalCommon.h" |
| |
| #define MAX_LENGTH 50 |
| #define MAX_PATH (256) |
| #define DEFAULT_HYSTERESIS 5000 |
| #define THERMAL_SYSFS "/sys/class/thermal/" |
| #define TZ_DIR_NAME "thermal_zone" |
| #define TZ_DIR_FMT "thermal_zone%d" |
| #define TEMPERATURE_FILE_FORMAT "/sys/class/thermal/thermal_zone%d/temp" |
| #define POLICY_FILE_FORMAT "/sys/class/thermal/thermal_zone%d/policy" |
| #define TRIP_FILE_FORMAT "/sys/class/thermal/thermal_zone%d/trip_point_1_temp" |
| #define HYST_FILE_FORMAT "/sys/class/thermal/thermal_zone%d/trip_point_1_hyst" |
| #define USER_SPACE_POLICY "user_space" |
| #define TZ_TYPE "type" |
| #define CDEV_DIR_NAME "cooling_device" |
| #define CDEV_DIR_FMT "cooling_device%d" |
| #define CDEV_CUR_STATE_PATH "/sys/class/thermal/cooling_device%d/cur_state" |
| #define CPU_USAGE_FILE "/proc/stat" |
| #define CPU_ONLINE_FILE_FORMAT "/sys/devices/system/cpu/cpu%d/online" |
| |
| namespace android { |
| namespace hardware { |
| namespace thermal { |
| namespace V2_0 { |
| namespace implementation { |
| |
| static std::unordered_map<std::string, cdevType> cdev_map = { |
| {"thermal-cpufreq-0", cdevType::CPU}, |
| {"thermal-cpufreq-1", cdevType::CPU}, |
| {"thermal-cpufreq-2", cdevType::CPU}, |
| {"thermal-cpufreq-3", cdevType::CPU}, |
| {"thermal-cpufreq-4", cdevType::CPU}, |
| {"thermal-cpufreq-5", cdevType::CPU}, |
| {"thermal-cpufreq-6", cdevType::CPU}, |
| {"thermal-cpufreq-7", cdevType::CPU}, |
| {"thermal-cluster-7-4", cdevType::CPU}, |
| {"cpu-isolate0", cdevType::CPU}, |
| {"cpu-isolate1", cdevType::CPU}, |
| {"cpu-isolate2", cdevType::CPU}, |
| {"cpu-isolate3", cdevType::CPU}, |
| {"cpu-isolate4", cdevType::CPU}, |
| {"cpu-isolate5", cdevType::CPU}, |
| {"cpu-isolate6", cdevType::CPU}, |
| {"cpu-isolate7", cdevType::CPU}, |
| {"thermal-devfreq-0", cdevType::GPU}, |
| {"modem_tj", cdevType::MODEM}, |
| {"cdsp", cdevType::NPU}, |
| {"cdsp_hw", cdevType::NPU}, |
| {"battery", cdevType::BATTERY}, |
| }; |
| |
| ThermalCommon::ThermalCommon() |
| { |
| LOG(DEBUG) << "Entering " << __func__; |
| ncpus = (int)sysconf(_SC_NPROCESSORS_CONF); |
| if (ncpus < 1) |
| LOG(ERROR) << "Error retrieving number of cores"; |
| } |
| |
| static int writeToFile(std::string_view path, std::string data) |
| { |
| std::fstream outFile; |
| |
| outFile.open(std::string(path).c_str(), |
| std::fstream::binary | std::fstream::out); |
| if (outFile.is_open()) { |
| LOG(DEBUG) << "writing: "<< data << " in path:" << path |
| << std::endl; |
| outFile << data; |
| outFile.close(); |
| return data.length(); |
| } |
| |
| LOG(ERROR) << "Error opening file: "<< path << std::endl; |
| return -1; |
| } |
| |
| static int readLineFromFile(std::string_view path, std::string& out) |
| { |
| char *fgets_ret; |
| FILE *fd; |
| int rv; |
| char buf[MAX_LENGTH]; |
| |
| out.clear(); |
| |
| fd = fopen(std::string(path).c_str(), "r"); |
| if (fd == NULL) { |
| LOG(ERROR) << "Path:" << std::string(path) << " file open error.err:" |
| << strerror(errno) << std::endl; |
| return errno; |
| } |
| |
| fgets_ret = fgets(buf, MAX_LENGTH, fd); |
| if (NULL != fgets_ret) { |
| rv = (int)strlen(buf); |
| out.append(buf, rv); |
| } else { |
| rv = ferror(fd); |
| } |
| |
| fclose(fd); |
| out.erase(std::remove(out.begin(), out.end(), '\n'), out.end()); |
| LOG(DEBUG) << "Path:" << std::string(path) << " Val:" << out << std::endl; |
| |
| return rv; |
| } |
| |
| int ThermalCommon::readFromFile(std::string_view path, std::string& out) |
| { |
| return readLineFromFile(path, out); |
| } |
| |
| static int get_tzn(std::string sensor_name) |
| { |
| DIR *tdir = NULL; |
| struct dirent *tdirent = NULL; |
| int found = -1; |
| int tzn = 0; |
| char name[MAX_PATH] = {0}; |
| char cwd[MAX_PATH] = {0}; |
| int ret = 0; |
| |
| if (!getcwd(cwd, sizeof(cwd))) |
| return found; |
| |
| /* Change dir to read the entries. Doesnt work otherwise */ |
| ret = chdir(THERMAL_SYSFS); |
| if (ret) { |
| LOG(ERROR) << "Unable to change to " << THERMAL_SYSFS << std::endl; |
| return found; |
| } |
| tdir = opendir(THERMAL_SYSFS); |
| if (!tdir) { |
| LOG(ERROR) << "Unable to open " << THERMAL_SYSFS << std::endl; |
| return found; |
| } |
| |
| while ((tdirent = readdir(tdir))) { |
| std::string buf; |
| |
| if (strncmp(tdirent->d_name, TZ_DIR_NAME, |
| strlen(TZ_DIR_NAME)) != 0) |
| continue; |
| |
| snprintf(name, MAX_PATH, "%s%s/%s", THERMAL_SYSFS, |
| tdirent->d_name, TZ_TYPE); |
| ret = readLineFromFile(std::string_view(name), buf); |
| if (ret <= 0) { |
| LOG(ERROR) << |
| "get_tzn: sensor name read error for tz:" << |
| tdirent->d_name << std::endl; |
| continue; |
| } |
| if (!strncmp(buf.c_str(), sensor_name.c_str(), |
| sensor_name.length())) { |
| found = 1; |
| break; |
| } |
| } |
| |
| if (found == 1) { |
| sscanf(tdirent->d_name, TZ_DIR_FMT, &tzn); |
| LOG(DEBUG) << "Sensor: " << sensor_name << |
| " found at tz: " << tzn << std::endl; |
| found = tzn; |
| } |
| |
| closedir(tdir); |
| /* Restore current working dir */ |
| ret = chdir(cwd); |
| |
| return found; |
| } |
| |
| int ThermalCommon::initialize_sensor(struct target_therm_cfg& cfg, int sens_idx) |
| { |
| struct therm_sensor sensor; |
| int idx = 0; |
| |
| sensor.tzn = get_tzn(cfg.sensor_list[sens_idx]); |
| if (sensor.tzn < 0) { |
| LOG(ERROR) << "No thermal zone for sensor: " << |
| cfg.sensor_list[sens_idx] << ", ret:" << |
| sensor.tzn << std::endl; |
| return -1; |
| } |
| if (cfg.type == TemperatureType::CPU) |
| sensor.thresh.name = sensor.t.name = |
| std::string("CPU") + std::to_string(sens_idx); |
| else |
| sensor.thresh.name = sensor.t.name = cfg.label; |
| |
| if (cfg.type == TemperatureType::BCL_PERCENTAGE) |
| sensor.mulFactor = 1; |
| else |
| sensor.mulFactor = 1000; |
| |
| sensor.sensor_name = cfg.sensor_list[sens_idx]; |
| sensor.positiveThresh = cfg.positive_thresh_ramp; |
| sensor.lastThrottleStatus = sensor.t.throttlingStatus = |
| ThrottlingSeverity::NONE; |
| sensor.thresh.type = sensor.t.type = cfg.type; |
| sensor.thresh.vrThrottlingThreshold = |
| UNKNOWN_TEMPERATURE; |
| for (idx = 0; idx <= (size_t)ThrottlingSeverity::SHUTDOWN; idx++) { |
| sensor.thresh.hotThrottlingThresholds[idx] = |
| sensor.thresh.coldThrottlingThresholds[idx] = |
| UNKNOWN_TEMPERATURE; |
| } |
| |
| if (cfg.throt_thresh != 0 && cfg.positive_thresh_ramp) |
| sensor.thresh.hotThrottlingThresholds[(size_t)ThrottlingSeverity::SEVERE] = |
| cfg.throt_thresh / (float)sensor.mulFactor; |
| else if (cfg.throt_thresh != 0 && !cfg.positive_thresh_ramp) |
| sensor.thresh.coldThrottlingThresholds[(size_t)ThrottlingSeverity::SEVERE] = |
| cfg.throt_thresh / (float)sensor.mulFactor; |
| |
| if (cfg.shutdwn_thresh != 0 && cfg.positive_thresh_ramp) |
| sensor.thresh.hotThrottlingThresholds[(size_t)ThrottlingSeverity::SHUTDOWN] = |
| cfg.shutdwn_thresh / (float)sensor.mulFactor; |
| else if (cfg.shutdwn_thresh != 0 && !cfg.positive_thresh_ramp) |
| sensor.thresh.coldThrottlingThresholds[(size_t)ThrottlingSeverity::SHUTDOWN] = |
| cfg.shutdwn_thresh / (float)sensor.mulFactor; |
| |
| if (cfg.vr_thresh != 0) |
| sensor.thresh.vrThrottlingThreshold = |
| cfg.vr_thresh / (float)sensor.mulFactor; |
| sens.push_back(sensor); |
| //read_temperature((struct therm_sensor *)sensor); |
| |
| return 0; |
| } |
| |
| int ThermalCommon::initializeCpuSensor(struct target_therm_cfg& cpu_cfg) |
| { |
| int cpu = 0; |
| |
| for (;cpu < ncpus; cpu++) { |
| if (initialize_sensor(cpu_cfg, cpu) < 0) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| int ThermalCommon::initThermalZones(std::vector<struct target_therm_cfg>& cfg) |
| { |
| std::vector<struct target_therm_cfg>::iterator it; |
| |
| if (cfg.empty()) { |
| LOG(ERROR) << std::string(__func__) +":Invalid input"; |
| return -1; |
| } |
| |
| for (it = cfg.begin(); it != cfg.end(); it++) |
| { |
| if (it->type == TemperatureType::CPU) { |
| if (initializeCpuSensor(*it) < 0) |
| return -1; |
| continue; |
| } |
| if (initialize_sensor(*it, 0) < 0) { |
| return -1; |
| } |
| } |
| |
| return sens.size(); |
| } |
| |
| int ThermalCommon::initCdev() |
| { |
| DIR *tdir = NULL; |
| struct dirent *tdirent = NULL; |
| int cdevn = 0; |
| char name[MAX_PATH] = {0}; |
| char cwd[MAX_PATH] = {0}; |
| int ret = 0; |
| |
| if (!getcwd(cwd, sizeof(cwd))) |
| return 0; |
| |
| /* Change dir to read the entries. Doesnt work otherwise */ |
| ret = chdir(THERMAL_SYSFS); |
| if (ret) { |
| LOG(ERROR) << "Unable to change to " << THERMAL_SYSFS << std::endl; |
| return 0; |
| } |
| tdir = opendir(THERMAL_SYSFS); |
| if (!tdir) { |
| LOG(ERROR) << "Unable to open " << THERMAL_SYSFS << std::endl; |
| return 0; |
| } |
| |
| while ((tdirent = readdir(tdir))) { |
| std::string buf; |
| struct dirent *tzdirent; |
| std::unordered_map<std::string, cdevType>::iterator it; |
| struct therm_cdev cdevInst; |
| |
| if (strncmp(tdirent->d_name, CDEV_DIR_NAME, |
| strlen(CDEV_DIR_NAME)) != 0) |
| continue; |
| |
| snprintf(name, MAX_PATH, "%s%s/%s", THERMAL_SYSFS, |
| tdirent->d_name, TZ_TYPE); |
| ret = readLineFromFile(std::string_view(name), buf); |
| if (ret <= 0) { |
| LOG(ERROR) << |
| "init_cdev: cdev type read error for cdev:" << |
| tdirent->d_name << std::endl; |
| } |
| it = cdev_map.find(buf); |
| if (it == cdev_map.end()) |
| continue; |
| sscanf(tdirent->d_name, CDEV_DIR_FMT, &cdevn); |
| LOG(DEBUG) << "cdev: " << it->first << |
| " found at cdev number: " << cdevn << std::endl; |
| cdevInst.c.name = it->first; |
| cdevInst.c.type = it->second; |
| cdevInst.cdevn = cdevn; |
| read_cdev_state(cdevInst); |
| cdev.push_back(cdevInst); |
| } |
| |
| closedir(tdir); |
| /* Restore current working dir */ |
| ret = chdir(cwd); |
| |
| return cdev.size(); |
| } |
| |
| int ThermalCommon::read_cdev_state(struct therm_cdev& cdev) |
| { |
| char file_name[MAX_PATH]; |
| std::string buf; |
| int ret = 0; |
| |
| LOG(DEBUG) << "Entering " <<__func__; |
| snprintf(file_name, sizeof(file_name), CDEV_CUR_STATE_PATH, |
| cdev.cdevn); |
| ret = readLineFromFile(std::string(file_name), buf); |
| if (ret <= 0) { |
| LOG(ERROR) << "Cdev state read error:"<< ret << |
| " for cdev: " << cdev.c.name; |
| return -1; |
| } |
| cdev.c.value = std::stoi(buf, nullptr, 0); |
| LOG(DEBUG) << "cdev Name:" << cdev.c.name << ". state:" << |
| cdev.c.value << std::endl; |
| |
| return cdev.c.value; |
| } |
| |
| int ThermalCommon::estimateSeverity(struct therm_sensor& sensor) |
| { |
| int idx = 0; |
| ThrottlingSeverity severity = ThrottlingSeverity::NONE; |
| float temp = sensor.t.value; |
| |
| for (idx = (int)ThrottlingSeverity::SHUTDOWN; idx >= 0; idx--) { |
| /* If a particular threshold is hit already, check if the |
| * hysteresis is cleared before changing the severity */ |
| if (idx == (int)sensor.t.throttlingStatus) { |
| if ((sensor.positiveThresh && |
| !isnan(sensor.thresh.hotThrottlingThresholds[idx]) && |
| temp >= |
| (sensor.thresh.hotThrottlingThresholds[idx] - |
| DEFAULT_HYSTERESIS / sensor.mulFactor)) || |
| (!sensor.positiveThresh && |
| !isnan(sensor.thresh.coldThrottlingThresholds[idx]) && |
| temp <= |
| (sensor.thresh.coldThrottlingThresholds[idx] + |
| DEFAULT_HYSTERESIS / sensor.mulFactor))) |
| break; |
| continue; |
| } |
| if ((sensor.positiveThresh && |
| !isnan(sensor.thresh.hotThrottlingThresholds[idx]) && |
| temp >= |
| sensor.thresh.hotThrottlingThresholds[idx]) || |
| (!sensor.positiveThresh && |
| !isnan(sensor.thresh.coldThrottlingThresholds[idx]) && |
| temp <= |
| sensor.thresh.coldThrottlingThresholds[idx])) |
| break; |
| } |
| if (idx >= 0) |
| severity = (ThrottlingSeverity)(idx); |
| LOG(DEBUG) << "Sensor Name:" << sensor.t.name << ". prev severity:" << |
| (int)sensor.lastThrottleStatus << ". cur severity:" << |
| (int)sensor.t.throttlingStatus << " New severity:" << |
| (int)severity << std::endl; |
| if (severity == sensor.t.throttlingStatus) |
| return -1; |
| sensor.lastThrottleStatus = sensor.t.throttlingStatus; |
| sensor.t.throttlingStatus = severity; |
| |
| return (int)severity; |
| } |
| |
| int ThermalCommon::read_temperature(struct therm_sensor& sensor) |
| { |
| char file_name[MAX_PATH]; |
| float temp; |
| std::string buf; |
| int ret = 0; |
| |
| LOG(DEBUG) << "Entering " <<__func__; |
| snprintf(file_name, sizeof(file_name), TEMPERATURE_FILE_FORMAT, |
| sensor.tzn); |
| ret = readLineFromFile(std::string(file_name), buf); |
| if (ret <= 0) { |
| LOG(ERROR) << "Temperature read error:"<< ret << |
| " for sensor " << sensor.t.name; |
| return -1; |
| } |
| sensor.t.value = (float)std::stoi(buf, nullptr, 0) / (float)sensor.mulFactor; |
| LOG(DEBUG) << "Sensor Name:" << sensor.t.name << ". Temperature:" << |
| (float)sensor.t.value << std::endl; |
| |
| return ret; |
| } |
| |
| void ThermalCommon::initThreshold(struct therm_sensor& sensor) |
| { |
| char file_name[MAX_PATH] = ""; |
| std::string buf; |
| int ret = 0, idx; |
| ThrottlingSeverity severity = ThrottlingSeverity::NONE; |
| int next_trip, curr_trip, hyst_temp = 0; |
| |
| LOG(DEBUG) << "Entering " <<__func__; |
| if (!sensor.positiveThresh) { |
| LOG(ERROR) << "negative temperature ramp for sensor:"<< |
| sensor.t.name; |
| return; |
| } |
| #ifndef ENABLE_THERMAL_NETLINK |
| snprintf(file_name, sizeof(file_name), POLICY_FILE_FORMAT, |
| sensor.tzn); |
| ret = readLineFromFile(std::string(file_name), buf); |
| if (ret <= 0) { |
| LOG(ERROR) << "Policy read error:"<< ret << |
| " for sensor " << sensor.t.name; |
| return; |
| } |
| if (buf != std::string(USER_SPACE_POLICY)) { |
| LOG(ERROR) << "Policy error:"<< buf << " sensor:" << |
| sensor.t.name << std::endl; |
| return; |
| } |
| #endif |
| next_trip = UNKNOWN_TEMPERATURE; |
| for (idx = 0;idx <= (int)ThrottlingSeverity::SHUTDOWN; idx++) { |
| if (isnan(sensor.thresh.hotThrottlingThresholds[idx]) |
| || idx <= (int)sensor.t.throttlingStatus) |
| continue; |
| |
| next_trip = sensor.thresh.hotThrottlingThresholds[idx] * |
| sensor.mulFactor; |
| break; |
| } |
| |
| if (!isnan(next_trip)) { |
| LOG(DEBUG) << "Sensor: " << sensor.t.name << " high trip:" |
| << next_trip << std::endl; |
| snprintf(file_name, sizeof(file_name), TRIP_FILE_FORMAT, |
| sensor.tzn); |
| writeToFile(std::string_view(file_name), std::to_string(next_trip)); |
| } |
| if (sensor.t.throttlingStatus != ThrottlingSeverity::NONE) { |
| curr_trip = sensor.thresh.hotThrottlingThresholds[ |
| (int)sensor.t.throttlingStatus] |
| * sensor.mulFactor; |
| if (!isnan(next_trip)) |
| hyst_temp = (next_trip - curr_trip) + DEFAULT_HYSTERESIS; |
| else |
| hyst_temp = DEFAULT_HYSTERESIS; |
| LOG(DEBUG) << "Sensor: " << sensor.t.name << " hysteresis:" |
| << hyst_temp << std::endl; |
| snprintf(file_name, sizeof(file_name), HYST_FILE_FORMAT, |
| sensor.tzn); |
| writeToFile(std::string_view(file_name), std::to_string(hyst_temp)); |
| } |
| |
| return; |
| } |
| |
| int ThermalCommon::get_cpu_usages(hidl_vec<CpuUsage>& list) { |
| int vals, cpu_num, online; |
| ssize_t read; |
| uint64_t user, nice, system, idle, active, total; |
| char *line = NULL; |
| size_t len = 0; |
| size_t cpu = 0; |
| char file_name[MAX_LENGTH]; |
| FILE *file; |
| FILE *cpu_file; |
| |
| list.resize(ncpus); |
| file = fopen(CPU_USAGE_FILE, "r"); |
| if (file == NULL) { |
| LOG(ERROR) << "failed to open:" << CPU_USAGE_FILE << |
| " err:" << strerror(errno); |
| return -errno; |
| } |
| |
| while ((read = getline(&line, &len, file)) != -1) { |
| if (strnlen(line, read) < 4 || strncmp(line, "cpu", 3) != 0 || |
| !isdigit(line[3])) { |
| free(line); |
| line = NULL; |
| len = 0; |
| continue; |
| } |
| vals = sscanf(line, \ |
| "cpu%d %" SCNu64 " %" SCNu64 " %" SCNu64 " %" SCNu64, \ |
| &cpu_num, &user, &nice, &system, &idle); |
| |
| free(line); |
| line = NULL; |
| len = 0; |
| |
| if (vals != 5 || cpu == ncpus) { |
| if (vals != 5) { |
| LOG(ERROR) << |
| "failed to read CPU information from file: " |
| << strerror(errno); |
| } else { |
| LOG(ERROR) << |
| "/proc/stat file has incorrect format."; |
| } |
| fclose(file); |
| return errno ? -errno : -EIO; |
| } |
| |
| active = user + nice + system; |
| total = active + idle; |
| |
| // Read online CPU information. |
| snprintf(file_name, MAX_LENGTH, CPU_ONLINE_FILE_FORMAT, |
| cpu_num); |
| cpu_file = fopen(file_name, "r"); |
| online = 0; |
| if (cpu_file == NULL) { |
| LOG(ERROR) << "failed to open file:" << file_name << |
| " err: " << strerror(errno); |
| fclose(file); |
| return -errno; |
| } |
| if (1 != fscanf(cpu_file, "%d", &online)) { |
| LOG(ERROR) << "failed to read CPU online information" << strerror(errno); |
| fclose(file); |
| fclose(cpu_file); |
| return errno ? -errno : -EIO; |
| } |
| fclose(cpu_file); |
| |
| list[cpu_num].name = std::string("CPU") + std::to_string(cpu_num); |
| list[cpu_num].active = active; |
| list[cpu_num].total = total; |
| list[cpu_num].isOnline = online; |
| cpu++; |
| } |
| fclose(file); |
| if (cpu != ncpus) { |
| LOG(ERROR) <<"/proc/stat file has incorrect format."; |
| return -EIO; |
| } |
| return ncpus; |
| } |
| |
| } // namespace implementation |
| } // namespace V2_0 |
| } // namespace thermal |
| } // namespace hardware |
| } // namespace android |