diff --git a/power/stats/1.0/default/Android.bp b/power/stats/1.0/default/Android.bp new file mode 100644 index 0000000000..04270c14fe --- /dev/null +++ b/power/stats/1.0/default/Android.bp @@ -0,0 +1,34 @@ +// Copyright (C) 2018 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 + +cc_library_shared { + name: "android.hardware.power.stats@1.0-service", + relative_install_path: "hw", + init_rc: ["android.hardware.power.stats@1.0-service.rc"], + srcs: ["service.cpp", "PowerStats.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libbase", + "libcutils", + "libfmq", + "libhidlbase", + "libhidltransport", + "liblog", + "libutils", + "android.hardware.power.stats@1.0", + ], + vendor: true, +} diff --git a/power/stats/1.0/default/PowerStats.cpp b/power/stats/1.0/default/PowerStats.cpp new file mode 100644 index 0000000000..810c575a87 --- /dev/null +++ b/power/stats/1.0/default/PowerStats.cpp @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "PowerStats.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace power { +namespace stats { +namespace V1_0 { +namespace implementation { + +#define MAX_FILE_PATH_LEN 128 +#define MAX_DEVICE_NAME_LEN 64 +#define MAX_QUEUE_SIZE 8192 + +constexpr char kIioDirRoot[] = "/sys/bus/iio/devices/"; +constexpr char kDeviceName[] = "pm_device_name"; +constexpr char kDeviceType[] = "iio:device"; +constexpr uint32_t MAX_SAMPLING_RATE = 10; +constexpr uint64_t WRITE_TIMEOUT_NS = 1000000000; + +void PowerStats::findIioPowerMonitorNodes() { + struct dirent* ent; + int fd; + char devName[MAX_DEVICE_NAME_LEN]; + char filePath[MAX_FILE_PATH_LEN]; + DIR* iioDir = opendir(kIioDirRoot); + if (!iioDir) { + ALOGE("Error opening directory: %s", kIioDirRoot); + return; + } + while (ent = readdir(iioDir), ent) { + if (strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0 && + strlen(ent->d_name) > strlen(kDeviceType) && + strncmp(ent->d_name, kDeviceType, strlen(kDeviceType)) == 0) { + snprintf(filePath, MAX_FILE_PATH_LEN, "%s/%s", ent->d_name, "name"); + fd = openat(dirfd(iioDir), filePath, O_RDONLY); + if (fd < 0) { + ALOGW("Failed to open directory: %s", filePath); + continue; + } + if (read(fd, devName, MAX_DEVICE_NAME_LEN) < 0) { + ALOGW("Failed to read device name from file: %s(%d)", filePath, fd); + close(fd); + continue; + } + + if (strncmp(devName, kDeviceName, strlen(kDeviceName)) == 0) { + snprintf(filePath, MAX_FILE_PATH_LEN, "%s/%s", kIioDirRoot, ent->d_name); + mPm.devicePaths.push_back(filePath); + } + close(fd); + } + } + closedir(iioDir); + return; +} + +size_t PowerStats::parsePowerRails() { + std::string data; + std::string railFileName; + std::string spsFileName; + uint32_t index = 0; + uint32_t samplingRate; + for (const auto& path : mPm.devicePaths) { + railFileName = path + "/enabled_rails"; + spsFileName = path + "/sampling_rate"; + if (!android::base::ReadFileToString(spsFileName, &data)) { + ALOGW("Error reading file: %s", spsFileName.c_str()); + continue; + } + samplingRate = strtoul(data.c_str(), NULL, 10); + if (!samplingRate || samplingRate == ULONG_MAX) { + ALOGE("Error parsing: %s", spsFileName.c_str()); + break; + } + if (!android::base::ReadFileToString(railFileName, &data)) { + ALOGW("Error reading file: %s", railFileName.c_str()); + continue; + } + std::istringstream railNames(data); + std::string line; + while (std::getline(railNames, line)) { + std::vector words = android::base::Split(line, ":"); + if (words.size() == 2) { + mPm.railsInfo.emplace(words[0], RailData{.devicePath = path, + .index = index, + .subsysName = words[1], + .samplingRate = samplingRate}); + index++; + } else { + ALOGW("Unexpected format in file: %s", railFileName.c_str()); + } + } + } + return index; +} + +int PowerStats::parseIioEnergyNode(std::string devName) { + int ret = 0; + std::string data; + std::string fileName = devName + "/energy_value"; + if (!android::base::ReadFileToString(fileName, &data)) { + ALOGE("Error reading file: %s", fileName.c_str()); + return -1; + } + + std::istringstream energyData(data); + std::string line; + uint64_t timestamp = 0; + bool timestampRead = false; + while (std::getline(energyData, line)) { + std::vector words = android::base::Split(line, ","); + if (timestampRead == false) { + if (words.size() == 1) { + timestamp = strtoull(words[0].c_str(), NULL, 10); + if (timestamp == 0 || timestamp == ULLONG_MAX) { + ALOGW("Potentially wrong timestamp: %" PRIu64, timestamp); + } + timestampRead = true; + } + } else if (words.size() == 2) { + std::string railName = words[0]; + if (mPm.railsInfo.count(railName) != 0) { + size_t index = mPm.railsInfo[railName].index; + mPm.reading[index].index = index; + mPm.reading[index].timestamp = timestamp; + mPm.reading[index].energy = strtoull(words[1].c_str(), NULL, 10); + if (mPm.reading[index].energy == ULLONG_MAX) { + ALOGW("Potentially wrong energy value: %" PRIu64, mPm.reading[index].energy); + } + } + } else { + ALOGW("Unexpected format in file: %s", fileName.c_str()); + ret = -1; + break; + } + } + return ret; +} + +Status PowerStats::parseIioEnergyNodes() { + Status ret = Status::SUCCESS; + if (mPm.hwEnabled == false) { + return Status::NOT_SUPPORTED; + } + + for (const auto& devicePath : mPm.devicePaths) { + if (parseIioEnergyNode(devicePath) < 0) { + ALOGE("Error in parsing power stats"); + ret = Status::FILESYSTEM_ERROR; + break; + } + } + return ret; +} + +PowerStats::PowerStats() { + findIioPowerMonitorNodes(); + size_t numRails = parsePowerRails(); + if (mPm.devicePaths.empty() || numRails == 0) { + mPm.hwEnabled = false; + } else { + mPm.hwEnabled = true; + mPm.reading.resize(numRails); + } +} + +Return PowerStats::getRailInfo(getRailInfo_cb _hidl_cb) { + hidl_vec rInfo; + Status ret = Status::SUCCESS; + size_t index; + std::lock_guard _lock(mPm.mLock); + if (mPm.hwEnabled == false) { + _hidl_cb(rInfo, Status::NOT_SUPPORTED); + return Void(); + } + rInfo.resize(mPm.railsInfo.size()); + for (const auto& railData : mPm.railsInfo) { + index = railData.second.index; + rInfo[index].railName = railData.first; + rInfo[index].subsysName = railData.second.subsysName; + rInfo[index].index = index; + rInfo[index].samplingRate = railData.second.samplingRate; + } + _hidl_cb(rInfo, ret); + return Void(); +} + +Return PowerStats::getEnergyData(const hidl_vec& railIndices, + getEnergyData_cb _hidl_cb) { + hidl_vec eVal; + std::lock_guard _lock(mPm.mLock); + Status ret = parseIioEnergyNodes(); + + if (ret != Status::SUCCESS) { + ALOGE("Failed to getEnergyData"); + _hidl_cb(eVal, ret); + return Void(); + } + + if (railIndices.size() == 0) { + eVal.resize(mPm.railsInfo.size()); + memcpy(&eVal[0], &mPm.reading[0], mPm.reading.size() * sizeof(EnergyData)); + } else { + eVal.resize(railIndices.size()); + int i = 0; + for (const auto& railIndex : railIndices) { + if (railIndex >= mPm.reading.size()) { + ret = Status::INVALID_INPUT; + eVal.resize(0); + break; + } + memcpy(&eVal[i], &mPm.reading[railIndex], sizeof(EnergyData)); + i++; + } + } + _hidl_cb(eVal, ret); + return Void(); +} + +Return PowerStats::streamEnergyData(uint32_t timeMs, uint32_t samplingRate, + streamEnergyData_cb _hidl_cb) { + std::lock_guard _lock(mPm.mLock); + if (mPm.fmqSynchronized != nullptr) { + _hidl_cb(MessageQueueSync::Descriptor(), 0, 0, Status::INSUFFICIENT_RESOURCES); + return Void(); + } + uint32_t sps = std::min(samplingRate, MAX_SAMPLING_RATE); + uint32_t numSamples = timeMs * sps / 1000; + mPm.fmqSynchronized.reset(new (std::nothrow) MessageQueueSync(MAX_QUEUE_SIZE, true)); + if (mPm.fmqSynchronized == nullptr || mPm.fmqSynchronized->isValid() == false) { + mPm.fmqSynchronized = nullptr; + _hidl_cb(MessageQueueSync::Descriptor(), 0, 0, Status::INSUFFICIENT_RESOURCES); + return Void(); + } + std::thread pollThread = std::thread([this, sps, numSamples]() { + uint64_t sleepTimeUs = 1000000 / sps; + uint32_t currSamples = 0; + while (currSamples < numSamples) { + mPm.mLock.lock(); + if (parseIioEnergyNodes() == Status::SUCCESS) { + mPm.fmqSynchronized->writeBlocking(&mPm.reading[0], mPm.reading.size(), + WRITE_TIMEOUT_NS); + mPm.mLock.unlock(); + currSamples++; + if (usleep(sleepTimeUs) < 0) { + ALOGW("Sleep interrupted"); + break; + } + } else { + mPm.mLock.unlock(); + break; + } + } + mPm.mLock.lock(); + mPm.fmqSynchronized = nullptr; + mPm.mLock.unlock(); + return; + }); + pollThread.detach(); + _hidl_cb(*(mPm.fmqSynchronized)->getDesc(), numSamples, mPm.reading.size(), Status::SUCCESS); + return Void(); +} + +Return PowerStats::getPowerEntityInfo(getPowerEntityInfo_cb _hidl_cb) { + hidl_vec eInfo; + _hidl_cb(eInfo, Status::NOT_SUPPORTED); + return Void(); +} + +Return PowerStats::getPowerEntityStateInfo(const hidl_vec& powerEntityIds, + getPowerEntityStateInfo_cb _hidl_cb) { + (void)powerEntityIds; + hidl_vec powerEntityStateSpaces; + _hidl_cb(powerEntityStateSpaces, Status::NOT_SUPPORTED); + return Void(); +} + +Return PowerStats::getPowerEntityStateResidencyData( + const hidl_vec& powerEntityIds, getPowerEntityStateResidencyData_cb _hidl_cb) { + (void)powerEntityIds; + hidl_vec results; + _hidl_cb(results, Status::NOT_SUPPORTED); + return Void(); +} + +} // namespace implementation +} // namespace V1_0 +} // namespace stats +} // namespace power +} // namespace hardware +} // namespace android diff --git a/power/stats/1.0/default/PowerStats.h b/power/stats/1.0/default/PowerStats.h new file mode 100644 index 0000000000..fb2c6a8ce5 --- /dev/null +++ b/power/stats/1.0/default/PowerStats.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifndef ANDROID_HARDWARE_POWERSTATS_V1_0_POWERSTATS_H +#define ANDROID_HARDWARE_POWERSTATS_V1_0_POWERSTATS_H + +#include +#include +#include +#include + +namespace android { +namespace hardware { +namespace power { +namespace stats { +namespace V1_0 { +namespace implementation { + +using ::android::hardware::hidl_vec; +using ::android::hardware::kSynchronizedReadWrite; +using ::android::hardware::Return; +using ::android::hardware::Void; +using ::android::hardware::power::stats::V1_0::EnergyData; +using ::android::hardware::power::stats::V1_0::PowerEntityInfo; +using ::android::hardware::power::stats::V1_0::PowerEntityStateInfo; +using ::android::hardware::power::stats::V1_0::PowerEntityStateResidencyData; +using ::android::hardware::power::stats::V1_0::PowerEntityStateResidencyResult; +using ::android::hardware::power::stats::V1_0::PowerEntityStateSpace; +using ::android::hardware::power::stats::V1_0::PowerEntityType; +using ::android::hardware::power::stats::V1_0::RailInfo; +using ::android::hardware::power::stats::V1_0::Status; + +typedef MessageQueue MessageQueueSync; +struct RailData { + std::string devicePath; + uint32_t index; + std::string subsysName; + uint32_t samplingRate; +}; + +struct OnDeviceMmt { + std::mutex mLock; + bool hwEnabled; + std::vector devicePaths; + std::map railsInfo; + std::vector reading; + std::unique_ptr fmqSynchronized; +}; + +struct PowerStats : public IPowerStats { + PowerStats(); + // Methods from ::android::hardware::power::stats::V1_0::IPowerStats follow. + Return getRailInfo(getRailInfo_cb _hidl_cb) override; + Return getEnergyData(const hidl_vec& railIndices, + getEnergyData_cb _hidl_cb) override; + Return streamEnergyData(uint32_t timeMs, uint32_t samplingRate, + streamEnergyData_cb _hidl_cb) override; + Return getPowerEntityInfo(getPowerEntityInfo_cb _hidl_cb) override; + Return getPowerEntityStateInfo(const hidl_vec& powerEntityIds, + getPowerEntityStateInfo_cb _hidl_cb) override; + Return getPowerEntityStateResidencyData( + const hidl_vec& powerEntityIds, + getPowerEntityStateResidencyData_cb _hidl_cb) override; + + private: + OnDeviceMmt mPm; + void findIioPowerMonitorNodes(); + size_t parsePowerRails(); + int parseIioEnergyNode(std::string devName); + Status parseIioEnergyNodes(); +}; + +} // namespace implementation +} // namespace V1_0 +} // namespace stats +} // namespace power +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_POWERSTATS_V1_0_POWERSTATS_H diff --git a/power/stats/1.0/default/android.hardware.power.stats@1.0-service.rc b/power/stats/1.0/default/android.hardware.power.stats@1.0-service.rc new file mode 100644 index 0000000000..d7e546b75e --- /dev/null +++ b/power/stats/1.0/default/android.hardware.power.stats@1.0-service.rc @@ -0,0 +1,4 @@ +service vendor.power.stats-hal-1-0 /vendor/bin/hw/android.hardware.power.stats@1.0-service + class hal + user system + group system diff --git a/power/stats/1.0/default/service.cpp b/power/stats/1.0/default/service.cpp new file mode 100644 index 0000000000..80649f5c5e --- /dev/null +++ b/power/stats/1.0/default/service.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 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 LOG_TAG "android.hardware.power.stats@1.0-service" + +#include +#include + +#include "PowerStats.h" + +using android::OK; +using android::sp; +using android::status_t; + +// libhwbinder: +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; + +// Generated HIDL files +using android::hardware::power::stats::V1_0::IPowerStats; +using android::hardware::power::stats::V1_0::implementation::PowerStats; + +int main(int /* argc */, char** /* argv */) { + ALOGI("power.stats service 1.0 is starting."); + + android::sp service = new PowerStats(); + if (service == nullptr) { + ALOGE("Can not create an instance of power.stats HAL Iface, exiting."); + return 1; + } + + configureRpcThreadpool(1, true /*callerWillJoin*/); + + status_t status = service->registerAsService(); + if (status != OK) { + ALOGE("Could not register service for power.stats HAL Iface (%d), exiting.", status); + return 1; + } + + ALOGI("power.stats service is ready"); + joinRpcThreadpool(); + + // In normal operation, we don't expect the thread pool to exit + ALOGE("power.stats service is shutting down"); + return 1; +}