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