1 /*
2  * Copyright (C) 2018 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 
17 #define LOG_TAG "libpixelpowerstats"
18 
19 #include <algorithm>
20 #include <thread>
21 #include <exception>
22 #include <inttypes.h>
23 #include <stdlib.h>
24 #include <android-base/file.h>
25 #include <android-base/logging.h>
26 #include <android-base/properties.h>
27 #include <android-base/strings.h>
28 #include <android-base/stringprintf.h>
29 #include "RailDataProvider.h"
30 
31 namespace android {
32 namespace hardware {
33 namespace google {
34 namespace pixel {
35 namespace powerstats {
36 
37 #define MAX_FILE_PATH_LEN 128
38 #define MAX_DEVICE_NAME_LEN 64
39 #define MAX_QUEUE_SIZE 8192
40 
41 constexpr char kIioDirRoot[] = "/sys/bus/iio/devices/";
42 constexpr char kDeviceName[] = "microchip,pac1934";
43 constexpr char kDeviceType[] = "iio:device";
44 constexpr uint32_t MAX_SAMPLING_RATE = 10;
45 constexpr uint64_t WRITE_TIMEOUT_NS = 1000000000;
46 
findIioPowerMonitorNodes()47 void RailDataProvider::findIioPowerMonitorNodes() {
48   struct dirent *ent;
49   int fd;
50   char devName[MAX_DEVICE_NAME_LEN];
51   char filePath[MAX_FILE_PATH_LEN];
52   DIR *iioDir = opendir(kIioDirRoot);
53   if (!iioDir) {
54     ALOGE("Error opening directory: %s, error: %d", kIioDirRoot, errno);
55     return;
56   }
57   while (ent = readdir(iioDir), ent) {
58     if (strcmp(ent->d_name, ".") != 0 &&
59         strcmp(ent->d_name, "..") != 0 &&
60         strlen(ent->d_name) > strlen(kDeviceType) &&
61         strncmp(ent->d_name, kDeviceType, strlen(kDeviceType)) == 0) {
62 
63       snprintf(filePath, MAX_FILE_PATH_LEN, "%s/%s", ent->d_name, "name");
64       fd = openat(dirfd(iioDir), filePath, O_RDONLY);
65       if (fd < 0) {
66         ALOGW("Failed to open directory: %s, error: %d", filePath, errno);
67         continue;
68       }
69       if (read(fd, devName, MAX_DEVICE_NAME_LEN) < 0) {
70         ALOGW("Failed to read device name from file: %s(%d)",
71               filePath, fd);
72         close(fd);
73         continue;
74       }
75 
76       if (strncmp(devName, kDeviceName, strlen(kDeviceName)) == 0) {
77         snprintf(filePath, MAX_FILE_PATH_LEN, "%s/%s", kIioDirRoot, ent->d_name);
78         mOdpm.devicePaths.push_back(filePath);
79       }
80       close(fd);
81     }
82   }
83   closedir(iioDir);
84   return;
85 }
86 
parsePowerRails()87 size_t RailDataProvider::parsePowerRails() {
88   std::string data;
89   std::string railFileName;
90   std::string spsFileName;
91   uint32_t index = 0;
92   uint32_t samplingRate;
93   for (const auto &path : mOdpm.devicePaths) {
94     railFileName = path + "/enabled_rails";
95     spsFileName = path + "/sampling_rate";
96     if (!android::base::ReadFileToString(spsFileName, &data)) {
97       ALOGW("Error reading file: %s", spsFileName.c_str());
98       continue;
99     }
100     samplingRate = strtoul(data.c_str(), NULL, 10);
101     if (!samplingRate || samplingRate == ULONG_MAX) {
102       ALOGE("Error parsing: %s", spsFileName.c_str());
103       break;
104     }
105     if (!android::base::ReadFileToString(railFileName, &data)) {
106       ALOGW("Error reading file: %s", railFileName.c_str());
107       continue;
108     }
109     std::istringstream railNames(data);
110     std::string line;
111     while (std::getline(railNames, line)) {
112       std::vector<std::string> words = android::base::Split(line, ":");
113       if (words.size() == 2) {
114         mOdpm.railsInfo.emplace(words[0],
115                            RailData {
116                              .devicePath = path,
117                              .index = index,
118                              .subsysName = words[1],
119                              .samplingRate = samplingRate
120                            });
121         index++;
122       } else {
123         ALOGW("Unexpected format in file: %s", railFileName.c_str());
124       }
125     }
126   }
127   return index;
128 }
129 
parseIioEnergyNode(std::string devName)130 int RailDataProvider::parseIioEnergyNode(std::string devName) {
131    int ret = 0;
132    std::string data;
133    std::string fileName = devName + "/energy_value";
134    if (!android::base::ReadFileToString(fileName, &data)) {
135      ALOGE("Error reading file: %s", fileName.c_str());
136      return -1;
137    }
138 
139    std::istringstream energyData(data);
140    std::string line;
141    uint64_t timestamp = 0;
142    bool timestampRead = false;
143    while (std::getline(energyData, line)) {
144      std::vector<std::string> words = android::base::Split(line, ",");
145      if (timestampRead == false) {
146        if (words.size() == 1) {
147          timestamp = strtoull(words[0].c_str(), NULL, 10);
148          if (timestamp == 0 || timestamp == ULLONG_MAX) {
149            ALOGW("Potentially wrong timestamp: %" PRIu64, timestamp);
150          }
151          timestampRead = true;
152        }
153      } else if (words.size() == 2) {
154          std::string railName = words[0];
155          if (mOdpm.railsInfo.count(railName) != 0) {
156            size_t index = mOdpm.railsInfo[railName].index;
157            mOdpm.reading[index].index = index;
158            mOdpm.reading[index].timestamp = timestamp;
159            mOdpm.reading[index].energy = strtoull(words[1].c_str(), NULL, 10);
160            if (mOdpm.reading[index].energy == ULLONG_MAX) {
161              ALOGW("Potentially wrong energy value: %" PRIu64,
162                    mOdpm.reading[index].energy);
163            }
164          }
165      } else {
166        ALOGW("Unexpected format in file: %s", fileName.c_str());
167        ret = -1;
168        break;
169      }
170    }
171    return ret;
172 }
173 
parseIioEnergyNodes()174 Status RailDataProvider::parseIioEnergyNodes() {
175   Status ret = Status::SUCCESS;
176   if (mOdpm.hwEnabled == false) {
177     return Status::NOT_SUPPORTED;
178   }
179 
180   for (const auto &devicePath : mOdpm.devicePaths) {
181     if(parseIioEnergyNode(devicePath) < 0) {
182       ALOGE("Error in parsing power stats");
183       ret = Status::FILESYSTEM_ERROR;
184       break;
185     }
186   }
187   return ret;
188 }
189 
RailDataProvider()190 RailDataProvider::RailDataProvider() {
191     findIioPowerMonitorNodes();
192     size_t numRails = parsePowerRails();
193     if (mOdpm.devicePaths.empty() || numRails == 0) {
194       mOdpm.hwEnabled = false;
195     } else {
196       mOdpm.hwEnabled = true;
197       mOdpm.reading.resize(numRails);
198     }
199 }
200 
getRailInfo(IPowerStats::getRailInfo_cb _hidl_cb)201 Return<void> RailDataProvider::getRailInfo(IPowerStats::getRailInfo_cb _hidl_cb) {
202   hidl_vec<RailInfo> rInfo;
203   Status ret = Status::SUCCESS;
204   size_t index;
205   std::lock_guard<std::mutex> _lock(mOdpm.mLock);
206   if (mOdpm.hwEnabled == false) {
207     ALOGI("getRailInfo not supported");
208     _hidl_cb(rInfo, Status::NOT_SUPPORTED);
209     return Void();
210   }
211   rInfo.resize(mOdpm.railsInfo.size());
212   for (const auto& railData : mOdpm.railsInfo) {
213     index = railData.second.index;
214     rInfo[index].railName = railData.first;
215     rInfo[index].subsysName = railData.second.subsysName;
216     rInfo[index].index = index;
217     rInfo[index].samplingRate = railData.second.samplingRate;
218   }
219   _hidl_cb(rInfo, ret);
220   return Void();
221 }
222 
getEnergyData(const hidl_vec<uint32_t> & railIndices,IPowerStats::getEnergyData_cb _hidl_cb)223 Return<void> RailDataProvider::getEnergyData(const hidl_vec<uint32_t>& railIndices, IPowerStats::getEnergyData_cb _hidl_cb) {
224   hidl_vec<EnergyData> eVal;
225   std::lock_guard<std::mutex> _lock(mOdpm.mLock);
226   Status ret = parseIioEnergyNodes();
227 
228   if (ret != Status::SUCCESS) {
229     ALOGE("Failed to getEnergyData");
230     _hidl_cb(eVal, ret);
231     return Void();
232   }
233 
234   if (railIndices.size() == 0) {
235     eVal.resize(mOdpm.railsInfo.size());
236     memcpy(&eVal[0], &mOdpm.reading[0], mOdpm.reading.size() * sizeof(EnergyData));
237   } else {
238     eVal.resize(railIndices.size());
239     int i = 0;
240     for (const auto &railIndex : railIndices) {
241       if (railIndex >= mOdpm.reading.size()) {
242         ret = Status::INVALID_INPUT;
243         eVal.resize(0);
244         break;
245       }
246       memcpy(&eVal[i], &mOdpm.reading[railIndex], sizeof(EnergyData));
247       i++;
248     }
249   }
250   _hidl_cb(eVal, ret);
251   return Void();
252 }
253 
streamEnergyData(uint32_t timeMs,uint32_t samplingRate,IPowerStats::streamEnergyData_cb _hidl_cb)254 Return<void> RailDataProvider::streamEnergyData(uint32_t timeMs, uint32_t samplingRate,
255                                 IPowerStats::streamEnergyData_cb _hidl_cb) {
256   std::lock_guard<std::mutex> _lock(mOdpm.mLock);
257   if (mOdpm.fmqSynchronized != nullptr) {
258     _hidl_cb(MessageQueueSync::Descriptor(),
259              0, 0, Status::INSUFFICIENT_RESOURCES);
260     return Void();
261   }
262   uint32_t sps = std::min(samplingRate, MAX_SAMPLING_RATE);
263   uint32_t numSamples = timeMs * sps / 1000;
264   mOdpm.fmqSynchronized.reset(new (std::nothrow) MessageQueueSync(MAX_QUEUE_SIZE, true));
265   if (mOdpm.fmqSynchronized == nullptr || mOdpm.fmqSynchronized->isValid() == false) {
266     mOdpm.fmqSynchronized = nullptr;
267     _hidl_cb(MessageQueueSync::Descriptor(),
268              0, 0, Status::INSUFFICIENT_RESOURCES);
269     return Void();
270   }
271   std::thread pollThread = std::thread([this, sps, numSamples]() {
272     uint64_t sleepTimeUs = 1000000/sps;
273     uint32_t currSamples = 0;
274     while (currSamples < numSamples) {
275       mOdpm.mLock.lock();
276       if (parseIioEnergyNodes() == Status::SUCCESS) {
277         mOdpm.fmqSynchronized->writeBlocking(&mOdpm.reading[0],
278                                              mOdpm.reading.size(), WRITE_TIMEOUT_NS);
279         mOdpm.mLock.unlock();
280         currSamples++;
281         if (usleep(sleepTimeUs) < 0) {
282           ALOGW("Sleep interrupted");
283           break;
284         }
285       } else {
286         mOdpm.mLock.unlock();
287         break;
288       }
289     }
290     mOdpm.mLock.lock();
291     mOdpm.fmqSynchronized = nullptr;
292     mOdpm.mLock.unlock();
293     return;
294   });
295   pollThread.detach();
296   _hidl_cb(*(mOdpm.fmqSynchronized)->getDesc(), numSamples,
297            mOdpm.reading.size(), Status::SUCCESS);
298   return Void();
299 }
300 
301 }  // namespace powerstats
302 }  // namespace pixel
303 }  // namespace google
304 }  // namespace hardware
305 }  // namespace android
306