| /* |
| * Copyright (C) 2019 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. |
| */ |
| #pragma once |
| |
| #include <android-base/stringprintf.h> |
| #include <android-base/unique_fd.h> |
| #include <log/log.h> |
| #include <sys/epoll.h> |
| #include <utils/Trace.h> |
| |
| #include <chrono> |
| #include <list> |
| #include <map> |
| #include <sstream> |
| #include <string> |
| #include <type_traits> |
| |
| #include "utils.h" |
| |
| namespace aidl { |
| namespace android { |
| namespace hardware { |
| namespace vibrator { |
| |
| using ::android::base::StringPrintf; |
| using ::android::base::unique_fd; |
| |
| class HwApiBase { |
| private: |
| using NamesMap = std::map<const std::ios *, std::string>; |
| |
| class RecordInterface { |
| public: |
| virtual std::string toString(const NamesMap &names) = 0; |
| virtual ~RecordInterface() {} |
| }; |
| template <typename T> |
| class Record : public RecordInterface { |
| public: |
| Record(const char *func, const T &value, const std::ios *stream) |
| : mFunc(func), |
| mValue(value), |
| mStream(stream), |
| mTp(std::chrono::system_clock::system_clock::now()) {} |
| std::string toString(const NamesMap &names) override; |
| |
| private: |
| const char *mFunc; |
| const T mValue; |
| const std::ios *mStream; |
| const std::chrono::system_clock::time_point mTp; |
| }; |
| using Records = std::list<std::unique_ptr<RecordInterface>>; |
| |
| static constexpr uint32_t RECORDS_SIZE = 2048; |
| |
| public: |
| HwApiBase(); |
| void debug(int fd); |
| |
| protected: |
| void updatePathPrefix(const std::string &prefix) { |
| ALOGI("Update HWAPI path prefix to %s", prefix.c_str()); |
| mPathPrefix = prefix; |
| } |
| void saveName(const std::string &name, const std::ios *stream); |
| template <typename T> |
| void open(const std::string &name, T *stream); |
| template <typename T> |
| bool has(const T &stream); |
| template <typename T> |
| bool get(T *value, std::istream *stream); |
| template <typename T> |
| bool set(const T &value, std::ostream *stream); |
| template <typename T> |
| bool poll(const T &value, std::istream *stream, const int32_t timeout = -1); |
| template <typename T> |
| void record(const char *func, const T &value, const std::ios *stream); |
| |
| private: |
| std::string mPathPrefix; |
| NamesMap mNames; |
| Records mRecords{RECORDS_SIZE}; |
| std::mutex mRecordsMutex; |
| std::mutex mIoMutex; |
| }; |
| |
| #define HWAPI_RECORD(args...) HwApiBase::record(__FUNCTION__, ##args) |
| |
| template <typename T> |
| void HwApiBase::open(const std::string &name, T *stream) { |
| saveName(name, stream); |
| utils::openNoCreate(mPathPrefix + name, stream); |
| } |
| |
| template <typename T> |
| bool HwApiBase::has(const T &stream) { |
| if constexpr (std::is_same<T, std::fstream>::value || std::is_same<T, std::ofstream>::value || |
| std::is_same<T, std::ifstream>::value) |
| return stream.is_open() && !stream.fail(); |
| |
| ALOGE("File stream is not of the correct type"); |
| return false; |
| } |
| |
| template <typename T> |
| bool HwApiBase::get(T *value, std::istream *stream) { |
| ATRACE_NAME("HwApi::get"); |
| std::scoped_lock ioLock{mIoMutex}; |
| bool ret; |
| stream->seekg(0); |
| *stream >> *value; |
| if (!(ret = !!*stream)) { |
| ALOGE("Failed to read %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); |
| } |
| stream->clear(); |
| HWAPI_RECORD(*value, stream); |
| return ret; |
| } |
| |
| template <typename T> |
| bool HwApiBase::set(const T &value, std::ostream *stream) { |
| ATRACE_NAME("HwApi::set"); |
| using utils::operator<<; |
| std::scoped_lock ioLock{mIoMutex}; |
| bool ret; |
| *stream << value << std::endl; |
| if (!(ret = !!*stream)) { |
| ALOGE("Failed to write %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); |
| stream->clear(); |
| } |
| HWAPI_RECORD(value, stream); |
| return ret; |
| } |
| |
| template <typename T> |
| bool HwApiBase::poll(const T &value, std::istream *stream, const int32_t timeoutMs) { |
| ATRACE_NAME(StringPrintf("HwApi::poll %s==%s", mNames[stream].c_str(), |
| std::to_string(value).c_str()) |
| .c_str()); |
| auto path = mPathPrefix + mNames[stream]; |
| unique_fd fileFd{::open(path.c_str(), O_RDONLY)}; |
| unique_fd epollFd{epoll_create(1)}; |
| epoll_event event = { |
| .events = EPOLLPRI | EPOLLET, |
| }; |
| T actual; |
| bool ret; |
| int epollRet; |
| |
| if (timeoutMs < -1) { |
| ALOGE("Invalid polling timeout!"); |
| return false; |
| } |
| |
| if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fileFd, &event)) { |
| ALOGE("Failed to poll %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); |
| return false; |
| } |
| |
| while ((ret = get(&actual, stream)) && (actual != value)) { |
| epollRet = epoll_wait(epollFd, &event, 1, timeoutMs); |
| if (epollRet <= 0) { |
| ALOGE("Polling error or timeout! (%d)", epollRet); |
| return false; |
| } |
| } |
| |
| HWAPI_RECORD(value, stream); |
| return ret; |
| } |
| |
| template <typename T> |
| void HwApiBase::record(const char *func, const T &value, const std::ios *stream) { |
| std::lock_guard<std::mutex> lock(mRecordsMutex); |
| mRecords.emplace_back(std::make_unique<Record<T>>(func, value, stream)); |
| mRecords.pop_front(); |
| } |
| |
| template <typename T> |
| std::string HwApiBase::Record<T>::toString(const NamesMap &names) { |
| using utils::operator<<; |
| std::stringstream ret; |
| auto lTp = std::chrono::system_clock::to_time_t(mTp); |
| struct tm buf; |
| auto lTime = localtime_r(&lTp, &buf); |
| |
| ret << std::put_time(lTime, "%Y-%m-%d %H:%M:%S.") << std::setfill('0') << std::setw(3) |
| << (std::chrono::duration_cast<std::chrono::milliseconds>(mTp.time_since_epoch()) % 1000) |
| .count() |
| << " " << mFunc << " '" << names.at(mStream) << "' = '" << mValue << "'"; |
| return ret.str(); |
| } |
| |
| class HwCalBase { |
| public: |
| HwCalBase(); |
| void debug(int fd); |
| |
| protected: |
| template <typename T> |
| bool getProperty(const char *key, T *value, const T defval); |
| template <typename T> |
| bool getPersist(const char *key, T *value); |
| |
| private: |
| std::string mPropertyPrefix; |
| std::map<std::string, std::string> mCalData; |
| }; |
| |
| template <typename T> |
| bool HwCalBase::getProperty(const char *key, T *outval, const T defval) { |
| ATRACE_NAME("HwCal::getProperty"); |
| *outval = utils::getProperty(mPropertyPrefix + key, defval); |
| return true; |
| } |
| |
| template <typename T> |
| bool HwCalBase::getPersist(const char *key, T *value) { |
| ATRACE_NAME("HwCal::getPersist"); |
| auto it = mCalData.find(key); |
| if (it == mCalData.end()) { |
| ALOGE("Missing %s config!", key); |
| return false; |
| } |
| std::stringstream stream{it->second}; |
| utils::unpack(stream, value); |
| if (!stream || !stream.eof()) { |
| ALOGE("Invalid %s config!", key); |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace vibrator |
| } // namespace hardware |
| } // namespace android |
| } // namespace aidl |