diff --git a/powerstats/Android.bp b/powerstats/Android.bp new file mode 100644 index 00000000..ecf5f794 --- /dev/null +++ b/powerstats/Android.bp @@ -0,0 +1,32 @@ +// +// Copyright (C) 2018 The Android Open Source Project +// +// SPDX-License-Identifier: Apache-2.0 +cc_binary { + name: "android.hardware.power.stats@1.0-service.pixel", + relative_install_path: "hw", + vintf_fragments: ["android.hardware.power.stats@1.0-service.pixel.xml"], + init_rc: ["android.hardware.power.stats@1.0-service.pixel.rc"], + srcs: ["service.cpp", + "RailDataProvider.cpp", + ], + cflags: [ + "-Wall", + "-Werror", + ], + static_libs: [ + "libpixelpowerstats", + ], + shared_libs: [ + "libbase", + "libcutils", + "libhidlbase", + "libfmq", + "liblog", + "libutils", + "android.hardware.power.stats@1.0", + "pixelpowerstats_provider_aidl_interface-cpp", + "libbinder", + ], + vendor: true, +} diff --git a/powerstats/RailDataProvider.cpp b/powerstats/RailDataProvider.cpp new file mode 100644 index 00000000..af6e43ad --- /dev/null +++ b/powerstats/RailDataProvider.cpp @@ -0,0 +1,285 @@ +/* + * 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 "libpixelpowerstats" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "RailDataProvider.h" +namespace android { +namespace hardware { +namespace google { +namespace pixel { +namespace powerstats { +#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[] = "microchip,pac1934"; +constexpr char kDeviceType[] = "iio:device"; +constexpr uint32_t MAX_SAMPLING_RATE = 10; +constexpr uint64_t WRITE_TIMEOUT_NS = 1000000000; +void RailDataProvider::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, error: %d", kIioDirRoot, errno); + 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, error: %d", filePath, errno); + 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); + mOdpm.devicePaths.push_back(filePath); + } + close(fd); + } + } + closedir(iioDir); + return; +} +size_t RailDataProvider::parsePowerRails() { + std::string data; + std::string railFileName; + std::string spsFileName; + uint32_t index = 0; + uint32_t samplingRate; + for (const auto &path : mOdpm.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) { + mOdpm.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 RailDataProvider::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 (mOdpm.railsInfo.count(railName) != 0) { + size_t index = mOdpm.railsInfo[railName].index; + mOdpm.reading[index].index = index; + mOdpm.reading[index].timestamp = timestamp; + mOdpm.reading[index].energy = strtoull(words[1].c_str(), NULL, 10); + if (mOdpm.reading[index].energy == ULLONG_MAX) { + ALOGW("Potentially wrong energy value: %" PRIu64, + mOdpm.reading[index].energy); + } + } + } else { + ALOGW("Unexpected format in file: %s", fileName.c_str()); + ret = -1; + break; + } + } + return ret; +} +Status RailDataProvider::parseIioEnergyNodes() { + Status ret = Status::SUCCESS; + if (mOdpm.hwEnabled == false) { + return Status::NOT_SUPPORTED; + } + for (const auto &devicePath : mOdpm.devicePaths) { + if(parseIioEnergyNode(devicePath) < 0) { + ALOGE("Error in parsing power stats"); + ret = Status::FILESYSTEM_ERROR; + break; + } + } + return ret; +} +RailDataProvider::RailDataProvider() { + findIioPowerMonitorNodes(); + size_t numRails = parsePowerRails(); + if (mOdpm.devicePaths.empty() || numRails == 0) { + mOdpm.hwEnabled = false; + } else { + mOdpm.hwEnabled = true; + mOdpm.reading.resize(numRails); + } +} +Return RailDataProvider::getRailInfo(IPowerStats::getRailInfo_cb _hidl_cb) { + hidl_vec rInfo; + Status ret = Status::SUCCESS; + size_t index; + std::lock_guard _lock(mOdpm.mLock); + if (mOdpm.hwEnabled == false) { + ALOGI("getRailInfo not supported"); + _hidl_cb(rInfo, Status::NOT_SUPPORTED); + return Void(); + } + rInfo.resize(mOdpm.railsInfo.size()); + for (const auto& railData : mOdpm.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 RailDataProvider::getEnergyData(const hidl_vec& railIndices, IPowerStats::getEnergyData_cb _hidl_cb) { + hidl_vec eVal; + std::lock_guard _lock(mOdpm.mLock); + Status ret = parseIioEnergyNodes(); + if (ret != Status::SUCCESS) { + ALOGE("Failed to getEnergyData"); + _hidl_cb(eVal, ret); + return Void(); + } + if (railIndices.size() == 0) { + eVal.resize(mOdpm.railsInfo.size()); + memcpy(&eVal[0], &mOdpm.reading[0], mOdpm.reading.size() * sizeof(EnergyData)); + } else { + eVal.resize(railIndices.size()); + int i = 0; + for (const auto &railIndex : railIndices) { + if (railIndex >= mOdpm.reading.size()) { + ret = Status::INVALID_INPUT; + eVal.resize(0); + break; + } + memcpy(&eVal[i], &mOdpm.reading[railIndex], sizeof(EnergyData)); + i++; + } + } + _hidl_cb(eVal, ret); + return Void(); +} +Return RailDataProvider::streamEnergyData(uint32_t timeMs, uint32_t samplingRate, + IPowerStats::streamEnergyData_cb _hidl_cb) { + std::lock_guard _lock(mOdpm.mLock); + if (mOdpm.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; + mOdpm.fmqSynchronized.reset(new (std::nothrow) MessageQueueSync(MAX_QUEUE_SIZE, true)); + if (mOdpm.fmqSynchronized == nullptr || mOdpm.fmqSynchronized->isValid() == false) { + mOdpm.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) { + mOdpm.mLock.lock(); + if (parseIioEnergyNodes() == Status::SUCCESS) { + mOdpm.fmqSynchronized->writeBlocking(&mOdpm.reading[0], + mOdpm.reading.size(), WRITE_TIMEOUT_NS); + mOdpm.mLock.unlock(); + currSamples++; + if (usleep(sleepTimeUs) < 0) { + ALOGW("Sleep interrupted"); + break; + } + } else { + mOdpm.mLock.unlock(); + break; + } + } + mOdpm.mLock.lock(); + mOdpm.fmqSynchronized = nullptr; + mOdpm.mLock.unlock(); + return; + }); + pollThread.detach(); + _hidl_cb(*(mOdpm.fmqSynchronized)->getDesc(), numSamples, + mOdpm.reading.size(), Status::SUCCESS); + return Void(); +} +} // namespace powerstats +} // namespace pixel +} // namespace google +} // namespace hardware +} // namespace android diff --git a/powerstats/RailDataProvider.h b/powerstats/RailDataProvider.h new file mode 100644 index 00000000..429d5320 --- /dev/null +++ b/powerstats/RailDataProvider.h @@ -0,0 +1,46 @@ +#ifndef ANDROID_HARDWARE_POWERSTATS_RAILDATAPROVIDER_H +#define ANDROID_HARDWARE_POWERSTATS_RAILDATAPROVIDER_H +#include +#include +namespace android { +namespace hardware { +namespace google { +namespace pixel { +namespace powerstats { +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; +}; +class RailDataProvider : public IRailDataProvider { +public: + RailDataProvider(); + // Methods from ::android::hardware::power::stats::V1_0::IPowerStats follow. + Return getRailInfo(IPowerStats::getRailInfo_cb _hidl_cb) override; + Return getEnergyData(const hidl_vec& railIndices, + IPowerStats::getEnergyData_cb _hidl_cb) override; + Return streamEnergyData(uint32_t timeMs, uint32_t samplingRate, + IPowerStats::streamEnergyData_cb _hidl_cb) override; + private: + OnDeviceMmt mOdpm; + void findIioPowerMonitorNodes(); + size_t parsePowerRails(); + int parseIioEnergyNode(std::string devName); + Status parseIioEnergyNodes(); +}; +} // namespace powerstats +} // namespace pixel +} // namespace google +} // namespace hardware +} // namespace android +#endif // ANDROID_HARDWARE_POWERSTATS_RAILDATAPROVIDER_H diff --git a/powerstats/android.hardware.power.stats@1.0-service.pixel.rc b/powerstats/android.hardware.power.stats@1.0-service.pixel.rc new file mode 100644 index 00000000..6ba83c28 --- /dev/null +++ b/powerstats/android.hardware.power.stats@1.0-service.pixel.rc @@ -0,0 +1,4 @@ +service vendor.power.stats-hal-1-0 /vendor/bin/hw/android.hardware.power.stats@1.0-service.pixel + class hal + user system + group system diff --git a/powerstats/android.hardware.power.stats@1.0-service.pixel.xml b/powerstats/android.hardware.power.stats@1.0-service.pixel.xml new file mode 100644 index 00000000..de084e2d --- /dev/null +++ b/powerstats/android.hardware.power.stats@1.0-service.pixel.xml @@ -0,0 +1,11 @@ + + + android.hardware.power.stats + hwbinder + 1.0 + + IPowerStats + default + + + diff --git a/powerstats/service.cpp b/powerstats/service.cpp new file mode 100644 index 00000000..bf4e7a50 --- /dev/null +++ b/powerstats/service.cpp @@ -0,0 +1,139 @@ +/* + * 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 LOG_TAG "android.hardware.power.stats@1.0-service.pixel" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "RailDataProvider.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::PowerEntityInfo; +using android::hardware::power::stats::V1_0::PowerEntityStateSpace; +using android::hardware::power::stats::V1_0::PowerEntityType; +using android::hardware::power::stats::V1_0::implementation::PowerStats; +// Pixel specific +using android::hardware::google::pixel::powerstats::AidlStateResidencyDataProvider; +using android::hardware::google::pixel::powerstats::GenericStateResidencyDataProvider; +using android::hardware::google::pixel::powerstats::PowerEntityConfig; +using android::hardware::google::pixel::powerstats::StateResidencyConfig; +using android::hardware::google::pixel::powerstats::RailDataProvider; +using android::hardware::google::pixel::powerstats::WlanStateResidencyDataProvider; +using android::hardware::google::pixel::powerstats::DisplayStateResidencyDataProvider; +int main(int /* argc */, char ** /* argv */) { + ALOGI("power.stats service 1.0 is starting."); + bool isDebuggable = android::base::GetBoolProperty("ro.debuggable", false); + PowerStats *service = new PowerStats(); + // Add rail data provider + service->setRailDataProvider(std::make_unique()); + // Add power entities related to rpmh + const uint64_t RPM_CLK = 19200; // RPM runs at 19.2Mhz. Divide by 19200 for msec + std::function rpmConvertToMs = [](uint64_t a) { return a / RPM_CLK; }; + std::vector rpmStateResidencyConfigs = { + {.name = "Sleep", + .entryCountSupported = true, + .entryCountPrefix = "Sleep Count:", + .totalTimeSupported = true, + .totalTimePrefix = "Sleep Accumulated Duration:", + .totalTimeTransform = rpmConvertToMs, + .lastEntrySupported = true, + .lastEntryPrefix = "Sleep Last Entered At:", + .lastEntryTransform = rpmConvertToMs}}; + sp rpmSdp = + new GenericStateResidencyDataProvider("/sys/power/rpmh_stats/master_stats"); + uint32_t apssId = service->addPowerEntity("APSS", PowerEntityType::SUBSYSTEM); + rpmSdp->addEntity(apssId, PowerEntityConfig("APSS", rpmStateResidencyConfigs)); + uint32_t mpssId = service->addPowerEntity("MPSS", PowerEntityType::SUBSYSTEM); + rpmSdp->addEntity(mpssId, PowerEntityConfig("MPSS", rpmStateResidencyConfigs)); + uint32_t adspId = service->addPowerEntity("ADSP", PowerEntityType::SUBSYSTEM); + rpmSdp->addEntity(adspId, PowerEntityConfig("ADSP", rpmStateResidencyConfigs)); + uint32_t adspIslandId = service->addPowerEntity("ADSP_ISLAND", PowerEntityType::SUBSYSTEM); + rpmSdp->addEntity(adspIslandId, PowerEntityConfig("ADSP_ISLAND", rpmStateResidencyConfigs)); + uint32_t cdspId = service->addPowerEntity("CDSP", PowerEntityType::SUBSYSTEM); + rpmSdp->addEntity(cdspId, PowerEntityConfig("CDSP", rpmStateResidencyConfigs)); + service->addStateResidencyDataProvider(std::move(rpmSdp)); + // Add SoC power entity + std::vector socStateResidencyConfigs = { + {.name = "AOSD", + .header = "RPM Mode:aosd", + .entryCountSupported = true, + .entryCountPrefix = "count:", + .totalTimeSupported = true, + .totalTimePrefix = "actual last sleep(msec):", + .lastEntrySupported = false}, + {.name = "CXSD", + .header = "RPM Mode:cxsd", + .entryCountSupported = true, + .entryCountPrefix = "count:", + .totalTimeSupported = true, + .totalTimePrefix = "actual last sleep(msec):", + .lastEntrySupported = false}}; + sp socSdp = + new GenericStateResidencyDataProvider("/sys/power/system_sleep/stats"); + uint32_t socId = service->addPowerEntity("SoC", PowerEntityType::POWER_DOMAIN); + socSdp->addEntity(socId, PowerEntityConfig(socStateResidencyConfigs)); + service->addStateResidencyDataProvider(socSdp); + if (isDebuggable) { + // Add WLAN power entity + uint32_t wlanId = service->addPowerEntity("WLAN", PowerEntityType::SUBSYSTEM); + sp wlanSdp = + new WlanStateResidencyDataProvider(wlanId, "/sys/kernel/wifi/power_stats"); + service->addStateResidencyDataProvider(wlanSdp); + } + uint32_t displayId = service->addPowerEntity("Display", PowerEntityType::SUBSYSTEM); + sp displaySdp = + new DisplayStateResidencyDataProvider(displayId, + "/sys/class/backlight/panel0-backlight/state", {"Off", "LP", "1080x2340@60", "1080x2340@90"}); + service->addStateResidencyDataProvider(displaySdp); + // Add Power Entities that require the Aidl data provider + sp aidlSdp = new AidlStateResidencyDataProvider(); + uint32_t citadelId = service->addPowerEntity("Citadel", PowerEntityType::SUBSYSTEM); + aidlSdp->addEntity(citadelId, "Citadel", {"Last-Reset", "Active", "Deep-Sleep"}); + auto serviceStatus = android::defaultServiceManager()->addService( + android::String16("power.stats-vendor"), aidlSdp); + if (serviceStatus != android::OK) { + ALOGE("Unable to register power.stats-vendor service %d", serviceStatus); + return 1; + } + sp ps{android::ProcessState::self()}; // Create non-HW binder threadpool + ps->startThreadPool(); + service->addStateResidencyDataProvider(aidlSdp); + // Configure the threadpool + 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; +}