| /* |
| * Copyright (C) 2020 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. |
| */ |
| |
| #define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL) |
| |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <dataproviders/IioEnergyMeterDataProvider.h> |
| #include <dataproviders/IioEnergyMeterDataSelector.h> |
| #include <inttypes.h> |
| #include <utils/Trace.h> |
| |
| namespace aidl { |
| namespace android { |
| namespace hardware { |
| namespace power { |
| namespace stats { |
| |
| using aidl::android::hardware::power::stats::IioEnergyMeterDataSelector; |
| |
| #define MAX_RAIL_NAME_LEN 50 |
| #define STR(s) #s |
| #define XSTR(s) STR(s) |
| |
| void IioEnergyMeterDataProvider::findIioEnergyMeterNodes() { |
| struct dirent *ent; |
| |
| DIR *iioDir = opendir(kIioRootDir.c_str()); |
| if (!iioDir) { |
| PLOG(ERROR) << "Error opening directory" << kIioRootDir; |
| return; |
| } |
| |
| // Find any iio:devices that match the given kDeviceNames |
| while (ent = readdir(iioDir), ent) { |
| std::string devTypeDir = ent->d_name; |
| if (devTypeDir.find(kDeviceType) != std::string::npos) { |
| std::string devicePath = kIioRootDir + devTypeDir; |
| std::string deviceNameContents; |
| |
| if (!::android::base::ReadFileToString(devicePath + kNameNode, &deviceNameContents)) { |
| LOG(WARNING) << "Failed to read device name from " << devicePath; |
| } else { |
| for (const auto &deviceName : kDeviceNames) { |
| if (deviceNameContents.find(deviceName) != std::string::npos) { |
| mDevicePaths.emplace(devicePath, deviceName); |
| } |
| } |
| } |
| } |
| } |
| |
| closedir(iioDir); |
| return; |
| } |
| |
| void IioEnergyMeterDataProvider::parseEnabledRails() { |
| std::string data; |
| int32_t id = 0; |
| for (const auto &path : mDevicePaths) { |
| // Get list of enabled rails |
| if (!::android::base::ReadFileToString(path.first + kEnabledRailsNode, &data)) { |
| LOG(ERROR) << "Error reading enabled rails from " << path.first; |
| continue; |
| } |
| |
| // Build RailInfos from list of enabled rails |
| std::istringstream railNames(data); |
| std::string line; |
| while (std::getline(railNames, line)) { |
| /* Format example: CH2[VSYS_PWR_RFFE]:Cellular */ |
| std::vector<std::string> words = ::android::base::Split(line, ":]["); |
| if (words.size() == 4) { |
| const std::string channelName = words[1]; |
| const std::string subsystemName = words[3]; |
| if (mChannelIds.count(channelName) == 0) { |
| mChannelInfos.push_back( |
| {.id = id, .name = channelName, .subsystem = subsystemName}); |
| mChannelIds.emplace(channelName, id); |
| id++; |
| } else { |
| LOG(WARNING) << "There exists rails with the same name (not supported): " |
| << channelName << ". Only the last occurrence of rail energy will " |
| << "be provided."; |
| } |
| } else { |
| LOG(WARNING) << "Unexpected enabled rail format in " << path.first; |
| } |
| } |
| } |
| } |
| |
| IioEnergyMeterDataProvider::IioEnergyMeterDataProvider( |
| const std::vector<const std::string> &deviceNames, const bool useSelector) |
| : kDeviceNames(std::move(deviceNames)) { |
| findIioEnergyMeterNodes(); |
| if (useSelector) { |
| /* Run meter selection in constructor; object can be discarded afterwards */ |
| IioEnergyMeterDataSelector selector(mDevicePaths); |
| } |
| parseEnabledRails(); |
| mReading.resize(mChannelInfos.size()); |
| } |
| |
| int IioEnergyMeterDataProvider::parseEnergyContents(const std::string &contents) { |
| std::istringstream energyData(contents); |
| std::string line; |
| |
| int ret = 0; |
| uint64_t timestamp = 0; |
| bool timestampRead = false; |
| |
| while (std::getline(energyData, line)) { |
| bool parseLineSuccess = false; |
| |
| if (timestampRead == false) { |
| /* Read timestamp from boot (ms) */ |
| if (sscanf(line.c_str(), "t=%" PRIu64, ×tamp) == 1) { |
| if (timestamp == 0 || timestamp == ULLONG_MAX) { |
| LOG(ERROR) << "Potentially wrong timestamp: " << timestamp; |
| } |
| timestampRead = true; |
| parseLineSuccess = true; |
| } |
| |
| } else { |
| /* Read rail energy */ |
| uint64_t energy = 0; |
| uint64_t duration = 0; |
| char railNameRaw[MAX_RAIL_NAME_LEN + 1]; |
| |
| /* Format example: CH3(T=358356)[S2M_VDD_CPUCL2], 761330 */ |
| if (sscanf(line.c_str(), |
| "CH%*d(T=%" PRIu64 ")[%" XSTR(MAX_RAIL_NAME_LEN) "[^]]], %" PRIu64, |
| &duration, railNameRaw, &energy) == 3) { |
| std::string railName(railNameRaw); |
| |
| /* If the count == 0, the rail may not be enabled */ |
| /* The count cannot be > 1; mChannelIds is a map */ |
| if (mChannelIds.count(railName) == 1) { |
| size_t index = mChannelIds[railName]; |
| mReading[index].id = index; |
| mReading[index].timestampMs = timestamp; |
| mReading[index].durationMs = duration; |
| mReading[index].energyUWs = energy; |
| if (mReading[index].energyUWs == ULLONG_MAX) { |
| LOG(ERROR) << "Potentially wrong energy value on rail: " << railName; |
| } |
| ATRACE_INT(railNameRaw, energy); |
| } |
| parseLineSuccess = true; |
| } |
| } |
| |
| if (parseLineSuccess == false) { |
| ret = -1; |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| int IioEnergyMeterDataProvider::parseEnergyValue(std::string path) { |
| int ret = 0; |
| std::string data; |
| if (!::android::base::ReadFileToString(path + kEnergyValueNode, &data)) { |
| LOG(ERROR) << "Error reading energy value in " << path; |
| return -1; |
| } |
| |
| ret = parseEnergyContents(data); |
| if (ret != 0) { |
| LOG(ERROR) << "Unexpected format in " << path; |
| } |
| return ret; |
| } |
| |
| ndk::ScopedAStatus IioEnergyMeterDataProvider::readEnergyMeter( |
| const std::vector<int32_t> &in_channelIds, std::vector<EnergyMeasurement> *_aidl_return) { |
| std::scoped_lock lock(mLock); |
| |
| for (const auto &devicePath : mDevicePaths) { |
| if (parseEnergyValue(devicePath.first) < 0) { |
| LOG(ERROR) << "Error in parsing " << devicePath.first; |
| return ndk::ScopedAStatus::ok(); |
| } |
| } |
| |
| if (in_channelIds.empty()) { |
| *_aidl_return = mReading; |
| } else { |
| _aidl_return->reserve(in_channelIds.size()); |
| for (const auto &id : in_channelIds) { |
| // check for invalid ids |
| if (id < 0 || id >= mChannelInfos.size()) { |
| return ndk::ScopedAStatus(AStatus_fromExceptionCode(EX_ILLEGAL_ARGUMENT)); |
| } |
| |
| _aidl_return->emplace_back(mReading[id]); |
| } |
| } |
| |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| ndk::ScopedAStatus IioEnergyMeterDataProvider::getEnergyMeterInfo( |
| std::vector<Channel> *_aidl_return) { |
| std::scoped_lock lk(mLock); |
| *_aidl_return = mChannelInfos; |
| |
| return ndk::ScopedAStatus::ok(); |
| } |
| |
| } // namespace stats |
| } // namespace power |
| } // namespace hardware |
| } // namespace android |
| } // namespace aidl |