1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #include <dirent.h>
17
18 #include <android-base/file.h>
19 #include <android-base/logging.h>
20 #include <android-base/stringprintf.h>
21 #include <android-base/strings.h>
22
23 #include "power_files.h"
24
25 namespace android {
26 namespace hardware {
27 namespace thermal {
28 namespace V2_0 {
29 namespace implementation {
30
31 constexpr std::string_view kDeviceType("iio:device");
32 constexpr std::string_view kIioRootDir("/sys/bus/iio/devices");
33 constexpr std::string_view kEnergyValueNode("energy_value");
34
35 using android::base::ReadFileToString;
36 using android::base::StringPrintf;
37
setPowerDataToDefault(std::string_view sensor_name)38 void PowerFiles::setPowerDataToDefault(std::string_view sensor_name) {
39 std::unique_lock<std::shared_mutex> _lock(throttling_release_map_mutex_);
40 if (!throttling_release_map_.count(sensor_name.data()) ||
41 !power_status_map_.count(sensor_name.data())) {
42 return;
43 }
44
45 auto &cdev_release_map = throttling_release_map_.at(sensor_name.data());
46 PowerSample power_sample = {};
47
48 for (auto &power_status_pair : power_status_map_.at(sensor_name.data())) {
49 for (size_t i = 0; i < power_status_pair.second.power_history.size(); ++i) {
50 for (size_t j = 0; j < power_status_pair.second.power_history[i].size(); ++j) {
51 power_status_pair.second.power_history[i].pop();
52 power_status_pair.second.power_history[i].emplace(power_sample);
53 }
54 }
55 power_status_pair.second.last_updated_avg_power = NAN;
56 }
57
58 for (auto &cdev_release_pair : cdev_release_map) {
59 cdev_release_pair.second.release_step = 0;
60 }
61 }
62
getReleaseStep(std::string_view sensor_name,std::string_view cdev_name)63 int PowerFiles::getReleaseStep(std::string_view sensor_name, std::string_view cdev_name) {
64 int release_step = 0;
65 std::shared_lock<std::shared_mutex> _lock(throttling_release_map_mutex_);
66
67 if (throttling_release_map_.count(sensor_name.data()) &&
68 throttling_release_map_[sensor_name.data()].count(cdev_name.data())) {
69 release_step = throttling_release_map_[sensor_name.data()][cdev_name.data()].release_step;
70 }
71
72 return release_step;
73 }
74
registerPowerRailsToWatch(std::string_view sensor_name,std::string_view cdev_name,const BindedCdevInfo & binded_cdev_info,const CdevInfo & cdev_info,const PowerRailInfo & power_rail_info)75 bool PowerFiles::registerPowerRailsToWatch(std::string_view sensor_name, std::string_view cdev_name,
76 const BindedCdevInfo &binded_cdev_info,
77 const CdevInfo &cdev_info,
78 const PowerRailInfo &power_rail_info) {
79 std::vector<std::queue<PowerSample>> power_history;
80 PowerSample power_sample = {
81 .energy_counter = 0,
82 .duration = 0,
83 };
84
85 if (throttling_release_map_.count(sensor_name.data()) &&
86 throttling_release_map_[sensor_name.data()].count(binded_cdev_info.power_rail)) {
87 return true;
88 }
89
90 if (!energy_info_map_.size() && !updateEnergyValues()) {
91 LOG(ERROR) << "Faield to update energy info";
92 return false;
93 }
94
95 if (power_rail_info.virtual_power_rail_info != nullptr &&
96 power_rail_info.virtual_power_rail_info->linked_power_rails.size()) {
97 for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
98 ++i) {
99 if (energy_info_map_.count(
100 power_rail_info.virtual_power_rail_info->linked_power_rails[i])) {
101 power_history.emplace_back(std::queue<PowerSample>());
102 for (int j = 0; j < power_rail_info.power_sample_count; j++) {
103 power_history[i].emplace(power_sample);
104 }
105 }
106 }
107 } else {
108 if (energy_info_map_.count(power_rail_info.rail)) {
109 power_history.emplace_back(std::queue<PowerSample>());
110 for (int j = 0; j < power_rail_info.power_sample_count; j++) {
111 power_history[0].emplace(power_sample);
112 }
113 }
114 }
115
116 if (power_history.size()) {
117 throttling_release_map_[sensor_name.data()][cdev_name.data()] = {
118 .release_step = 0,
119 .max_release_step = cdev_info.max_state,
120 };
121 power_status_map_[sensor_name.data()][binded_cdev_info.power_rail] = {
122 .power_history = power_history,
123 .time_remaining = power_rail_info.power_sample_delay,
124 .last_updated_avg_power = NAN,
125 };
126 } else {
127 return false;
128 }
129
130 LOG(INFO) << "Sensor " << sensor_name.data() << " successfully registers power rail "
131 << binded_cdev_info.power_rail << " for cooling device " << cdev_name.data();
132 return true;
133 }
134
findEnergySourceToWatch(void)135 bool PowerFiles::findEnergySourceToWatch(void) {
136 std::string devicePath;
137
138 if (energy_path_set_.size()) {
139 return true;
140 }
141
142 std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(kIioRootDir.data()), closedir);
143 if (!dir) {
144 PLOG(ERROR) << "Error opening directory" << kIioRootDir;
145 return false;
146 }
147
148 // Find any iio:devices that support energy_value
149 while (struct dirent *ent = readdir(dir.get())) {
150 std::string devTypeDir = ent->d_name;
151 if (devTypeDir.find(kDeviceType) != std::string::npos) {
152 devicePath = StringPrintf("%s/%s", kIioRootDir.data(), devTypeDir.data());
153 std::string deviceEnergyContent;
154
155 if (!ReadFileToString(StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()),
156 &deviceEnergyContent)) {
157 } else if (deviceEnergyContent.size()) {
158 energy_path_set_.emplace(
159 StringPrintf("%s/%s", devicePath.data(), kEnergyValueNode.data()));
160 }
161 }
162 }
163
164 if (!energy_path_set_.size()) {
165 return false;
166 }
167
168 return true;
169 }
170
clearEnergyInfoMap(void)171 void PowerFiles::clearEnergyInfoMap(void) {
172 energy_info_map_.clear();
173 }
174
updateEnergyValues(void)175 bool PowerFiles::updateEnergyValues(void) {
176 std::string deviceEnergyContent;
177 std::string deviceEnergyContents;
178 std::string line;
179
180 for (const auto &path : energy_path_set_) {
181 if (!android::base::ReadFileToString(path, &deviceEnergyContent)) {
182 LOG(ERROR) << "Failed to read energy content from " << path;
183 return false;
184 } else {
185 deviceEnergyContents.append(deviceEnergyContent);
186 }
187 }
188
189 std::istringstream energyData(deviceEnergyContents);
190
191 clearEnergyInfoMap();
192 while (std::getline(energyData, line)) {
193 /* Read rail energy */
194 uint64_t energy_counter = 0;
195 uint64_t duration = 0;
196
197 /* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */
198 auto start_pos = line.find("T=");
199 auto end_pos = line.find(')');
200 if (start_pos != std::string::npos) {
201 duration =
202 strtoul(line.substr(start_pos + 2, end_pos - start_pos - 2).c_str(), NULL, 10);
203 } else {
204 continue;
205 }
206
207 start_pos = line.find(")[");
208 end_pos = line.find(']');
209 std::string railName;
210 if (start_pos != std::string::npos) {
211 railName = line.substr(start_pos + 2, end_pos - start_pos - 2);
212 } else {
213 continue;
214 }
215
216 start_pos = line.find("],");
217 if (start_pos != std::string::npos) {
218 energy_counter = strtoul(line.substr(start_pos + 2).c_str(), NULL, 10);
219 } else {
220 continue;
221 }
222
223 energy_info_map_[railName] = {
224 .energy_counter = energy_counter,
225 .duration = duration,
226 };
227 }
228
229 return true;
230 }
231
getAveragePower(std::string_view power_rail,std::queue<PowerSample> * power_history,bool power_sample_update,float * avg_power)232 bool PowerFiles::getAveragePower(std::string_view power_rail,
233 std::queue<PowerSample> *power_history, bool power_sample_update,
234 float *avg_power) {
235 const auto curr_sample = energy_info_map_.at(power_rail.data());
236 bool ret = true;
237
238 const auto last_sample = power_history->front();
239 const auto duration = curr_sample.duration - last_sample.duration;
240 const auto deltaEnergy = curr_sample.energy_counter - last_sample.energy_counter;
241
242 if (!last_sample.duration) {
243 LOG(VERBOSE) << "Power rail " << power_rail.data() << ": the last energy timestamp is zero";
244 } else if (duration <= 0 || deltaEnergy < 0) {
245 LOG(ERROR) << "Power rail " << power_rail.data() << " is invalid: duration = " << duration
246 << ", deltaEnergy = " << deltaEnergy;
247
248 ret = false;
249 } else {
250 *avg_power = static_cast<float>(deltaEnergy) / static_cast<float>(duration);
251 LOG(VERBOSE) << "Power rail " << power_rail.data() << ", avg power = " << *avg_power
252 << ", duration = " << duration << ", deltaEnergy = " << deltaEnergy;
253 }
254
255 if (power_sample_update) {
256 power_history->pop();
257 power_history->push(curr_sample);
258 }
259
260 return ret;
261 }
262
computeAveragePower(const PowerRailInfo & power_rail_info,PowerStatus * power_status,bool power_sample_update,float * avg_power)263 bool PowerFiles::computeAveragePower(const PowerRailInfo &power_rail_info,
264 PowerStatus *power_status, bool power_sample_update,
265 float *avg_power) {
266 bool ret = true;
267
268 float avg_power_val = -1;
269 float offset = power_rail_info.virtual_power_rail_info->offset;
270 for (size_t i = 0; i < power_rail_info.virtual_power_rail_info->linked_power_rails.size();
271 i++) {
272 float coefficient = power_rail_info.virtual_power_rail_info->coefficients[i];
273 float avg_power_number = -1;
274 if (!getAveragePower(power_rail_info.virtual_power_rail_info->linked_power_rails[i],
275 &power_status->power_history[i], power_sample_update,
276 &avg_power_number)) {
277 ret = false;
278 continue;
279 } else if (avg_power_number < 0) {
280 continue;
281 }
282 switch (power_rail_info.virtual_power_rail_info->formula) {
283 case FormulaOption::COUNT_THRESHOLD:
284 if ((coefficient < 0 && avg_power_number < -coefficient) ||
285 (coefficient >= 0 && avg_power_number >= coefficient))
286 avg_power_val += 1;
287 break;
288 case FormulaOption::WEIGHTED_AVG:
289 avg_power_val += avg_power_number * coefficient;
290 break;
291 case FormulaOption::MAXIMUM:
292 if (i == 0)
293 avg_power_val = std::numeric_limits<float>::lowest();
294 if (avg_power_number * coefficient > avg_power_val)
295 avg_power_val = avg_power_number * coefficient;
296 break;
297 case FormulaOption::MINIMUM:
298 if (i == 0)
299 avg_power_val = std::numeric_limits<float>::max();
300 if (avg_power_number * coefficient < avg_power_val)
301 avg_power_val = avg_power_number * coefficient;
302 break;
303 default:
304 break;
305 }
306 }
307 if (avg_power_val >= 0) {
308 avg_power_val = avg_power_val + offset;
309 }
310
311 *avg_power = avg_power_val;
312 return ret;
313 }
314
throttlingReleaseUpdate(std::string_view sensor_name,std::string_view cdev_name,const ThrottlingSeverity severity,const std::chrono::milliseconds time_elapsed_ms,const BindedCdevInfo & binded_cdev_info,const PowerRailInfo & power_rail_info,bool power_sample_update,bool severity_changed)315 bool PowerFiles::throttlingReleaseUpdate(std::string_view sensor_name, std::string_view cdev_name,
316 const ThrottlingSeverity severity,
317 const std::chrono::milliseconds time_elapsed_ms,
318 const BindedCdevInfo &binded_cdev_info,
319 const PowerRailInfo &power_rail_info,
320 bool power_sample_update, bool severity_changed) {
321 std::unique_lock<std::shared_mutex> _lock(throttling_release_map_mutex_);
322 float avg_power = -1;
323
324 if (!throttling_release_map_.count(sensor_name.data()) ||
325 !throttling_release_map_[sensor_name.data()].count(cdev_name.data()) ||
326 !power_status_map_.count(sensor_name.data()) ||
327 !power_status_map_[sensor_name.data()].count(binded_cdev_info.power_rail)) {
328 return false;
329 }
330
331 auto &release_status = throttling_release_map_[sensor_name.data()].at(cdev_name.data());
332 auto &power_status = power_status_map_[sensor_name.data()].at(binded_cdev_info.power_rail);
333
334 if (power_sample_update) {
335 if (time_elapsed_ms > power_status.time_remaining) {
336 power_status.time_remaining = power_rail_info.power_sample_delay;
337 } else {
338 power_status.time_remaining = power_status.time_remaining - time_elapsed_ms;
339 LOG(VERBOSE) << "Power rail " << binded_cdev_info.power_rail
340 << " : timeout remaining = " << power_status.time_remaining.count();
341 if (!severity_changed) {
342 return true;
343 } else {
344 // get the cached average power when thermal severity is changed
345 power_sample_update = false;
346 }
347 }
348 } else if (!severity_changed &&
349 power_status.time_remaining != power_rail_info.power_sample_delay) {
350 return false;
351 }
352
353 if (!energy_info_map_.size() && !updateEnergyValues()) {
354 LOG(ERROR) << "Failed to update energy values";
355 release_status.release_step = 0;
356 return false;
357 }
358
359 if (!power_sample_update && !std::isnan(power_status.last_updated_avg_power)) {
360 avg_power = power_status.last_updated_avg_power;
361 } else {
362 // Return false if we cannot get the average power of the target power rail
363 if (!((power_rail_info.virtual_power_rail_info == nullptr)
364 ? getAveragePower(binded_cdev_info.power_rail, &power_status.power_history[0],
365 power_sample_update, &avg_power)
366 : computeAveragePower(power_rail_info, &power_status, power_sample_update,
367 &avg_power))) {
368 release_status.release_step = 0;
369 if (binded_cdev_info.throttling_with_power_link) {
370 release_status.release_step = release_status.max_release_step;
371 }
372 return false;
373 } else if (avg_power < 0) {
374 if (binded_cdev_info.throttling_with_power_link) {
375 release_status.release_step = release_status.max_release_step;
376 }
377 return true;
378 }
379 }
380
381 power_status.last_updated_avg_power = avg_power;
382 bool is_over_budget = true;
383 if (!binded_cdev_info.high_power_check) {
384 if (avg_power < binded_cdev_info.power_thresholds[static_cast<int>(severity)]) {
385 is_over_budget = false;
386 }
387 } else {
388 if (avg_power > binded_cdev_info.power_thresholds[static_cast<int>(severity)]) {
389 is_over_budget = false;
390 }
391 }
392 LOG(INFO) << "Power rail " << binded_cdev_info.power_rail << ": power threshold = "
393 << binded_cdev_info.power_thresholds[static_cast<int>(severity)]
394 << ", avg power = " << avg_power;
395
396 switch (binded_cdev_info.release_logic) {
397 case ReleaseLogic::INCREASE:
398 if (!is_over_budget) {
399 if (std::abs(release_status.release_step) <
400 static_cast<int>(release_status.max_release_step)) {
401 release_status.release_step--;
402 }
403 } else {
404 release_status.release_step = 0;
405 }
406 break;
407 case ReleaseLogic::DECREASE:
408 if (!is_over_budget) {
409 if (release_status.release_step <
410 static_cast<int>(release_status.max_release_step)) {
411 release_status.release_step++;
412 }
413 } else {
414 release_status.release_step = 0;
415 }
416 break;
417 case ReleaseLogic::STEPWISE:
418 if (!is_over_budget) {
419 if (release_status.release_step <
420 static_cast<int>(release_status.max_release_step)) {
421 release_status.release_step++;
422 }
423 } else {
424 if (std::abs(release_status.release_step) <
425 static_cast<int>(release_status.max_release_step)) {
426 release_status.release_step--;
427 }
428 }
429 break;
430 case ReleaseLogic::RELEASE_TO_FLOOR:
431 release_status.release_step = is_over_budget ? 0 : release_status.max_release_step;
432 break;
433 case ReleaseLogic::NONE:
434 default:
435 break;
436 }
437 return true;
438 }
439
440 } // namespace implementation
441 } // namespace V2_0
442 } // namespace thermal
443 } // namespace hardware
444 } // namespace android
445