| /* |
| * Copyright 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 "SystemSuspend.h" |
| |
| #include <aidl/android/system/suspend/ISystemSuspend.h> |
| #include <aidl/android/system/suspend/IWakeLock.h> |
| #include <android-base/file.h> |
| #include <android-base/logging.h> |
| #include <android-base/stringprintf.h> |
| #include <android-base/strings.h> |
| #include <android/binder_manager.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <chrono> |
| #include <string> |
| #include <thread> |
| using namespace std::chrono_literals; |
| |
| using ::aidl::android::system::suspend::ISystemSuspend; |
| using ::aidl::android::system::suspend::IWakeLock; |
| using ::aidl::android::system::suspend::WakeLockType; |
| using ::android::base::Error; |
| using ::android::base::ReadFdToString; |
| using ::android::base::WriteStringToFd; |
| using ::std::string; |
| |
| namespace android { |
| namespace system { |
| namespace suspend { |
| namespace V1_0 { |
| |
| struct SuspendTime { |
| std::chrono::nanoseconds suspendOverhead; |
| std::chrono::nanoseconds suspendTime; |
| }; |
| |
| static const char kSleepState[] = "mem"; |
| // TODO(b/128923994): we only need /sys/power/wake_[un]lock to export debugging info via |
| // /sys/kernel/debug/wakeup_sources. |
| static constexpr char kSysPowerWakeLock[] = "/sys/power/wake_lock"; |
| static constexpr char kSysPowerWakeUnlock[] = "/sys/power/wake_unlock"; |
| static constexpr char kUnknownWakeup[] = "unknown"; |
| |
| // This function assumes that data in fd is small enough that it can be read in one go. |
| // We use this function instead of the ones available in libbase because it doesn't block |
| // indefinitely when reading from socket streams which are used for testing. |
| string readFd(int fd) { |
| char buf[BUFSIZ]; |
| ssize_t n = TEMP_FAILURE_RETRY(read(fd, &buf[0], sizeof(buf))); |
| if (n < 0) return ""; |
| return string{buf, static_cast<size_t>(n)}; |
| } |
| |
| static std::vector<std::string> readWakeupReasons(int fd) { |
| std::vector<std::string> wakeupReasons; |
| std::string reasonlines; |
| |
| lseek(fd, 0, SEEK_SET); |
| if (!ReadFdToString(fd, &reasonlines) || reasonlines.empty()) { |
| PLOG(ERROR) << "failed to read wakeup reasons"; |
| // Return unknown wakeup reason if we fail to read |
| return {kUnknownWakeup}; |
| } |
| |
| std::stringstream ss(reasonlines); |
| for (std::string reasonline; std::getline(ss, reasonline);) { |
| reasonline = ::android::base::Trim(reasonline); |
| |
| // Only include non-empty reason lines |
| if (!reasonline.empty()) { |
| wakeupReasons.push_back(reasonline); |
| } |
| } |
| |
| // Empty wakeup reason found. Record as unknown wakeup |
| if (wakeupReasons.empty()) { |
| wakeupReasons.push_back(kUnknownWakeup); |
| } |
| |
| return wakeupReasons; |
| } |
| |
| // reads the suspend overhead and suspend time |
| // Returns 0s if reading the sysfs node fails (unlikely) |
| static struct SuspendTime readSuspendTime(int fd) { |
| std::string content; |
| |
| lseek(fd, 0, SEEK_SET); |
| if (!ReadFdToString(fd, &content)) { |
| LOG(ERROR) << "failed to read suspend time"; |
| return {0ns, 0ns}; |
| } |
| |
| double suspendOverhead, suspendTime; |
| std::stringstream ss(content); |
| if (!(ss >> suspendOverhead) || !(ss >> suspendTime)) { |
| LOG(ERROR) << "failed to parse suspend time " << content; |
| return {0ns, 0ns}; |
| } |
| |
| return {std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::duration<double>(suspendOverhead)), |
| std::chrono::duration_cast<std::chrono::nanoseconds>( |
| std::chrono::duration<double>(suspendTime))}; |
| } |
| |
| SystemSuspend::SystemSuspend(unique_fd wakeupCountFd, unique_fd stateFd, unique_fd suspendStatsFd, |
| size_t maxStatsEntries, unique_fd kernelWakelockStatsFd, |
| unique_fd wakeupReasonsFd, unique_fd suspendTimeFd, |
| const SleepTimeConfig& sleepTimeConfig, |
| const sp<SuspendControlService>& controlService, |
| const sp<SuspendControlServiceInternal>& controlServiceInternal, |
| bool useSuspendCounter) |
| : mSuspendCounter(0), |
| mWakeupCountFd(std::move(wakeupCountFd)), |
| mStateFd(std::move(stateFd)), |
| mSuspendStatsFd(std::move(suspendStatsFd)), |
| mSuspendTimeFd(std::move(suspendTimeFd)), |
| kSleepTimeConfig(sleepTimeConfig), |
| mSleepTime(sleepTimeConfig.baseSleepTime), |
| mNumConsecutiveBadSuspends(0), |
| mControlService(controlService), |
| mControlServiceInternal(controlServiceInternal), |
| mStatsList(maxStatsEntries, std::move(kernelWakelockStatsFd)), |
| mWakeupList(maxStatsEntries), |
| mUseSuspendCounter(useSuspendCounter), |
| mWakeLockFd(-1), |
| mWakeUnlockFd(-1), |
| mWakeupReasonsFd(std::move(wakeupReasonsFd)) { |
| mControlServiceInternal->setSuspendService(this); |
| |
| if (!mUseSuspendCounter) { |
| mWakeLockFd.reset(TEMP_FAILURE_RETRY(open(kSysPowerWakeLock, O_CLOEXEC | O_RDWR))); |
| if (mWakeLockFd < 0) { |
| PLOG(ERROR) << "error opening " << kSysPowerWakeLock; |
| } |
| mWakeUnlockFd.reset(TEMP_FAILURE_RETRY(open(kSysPowerWakeUnlock, O_CLOEXEC | O_RDWR))); |
| if (mWakeUnlockFd < 0) { |
| PLOG(ERROR) << "error opening " << kSysPowerWakeUnlock; |
| } |
| } |
| } |
| |
| bool SystemSuspend::enableAutosuspend(const sp<IBinder>& token) { |
| auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock); |
| auto autosuspendLock = std::lock_guard(mAutosuspendLock); |
| |
| bool hasToken = std::find(mAutosuspendClientTokens.begin(), mAutosuspendClientTokens.end(), |
| token) != mAutosuspendClientTokens.end(); |
| |
| if (!hasToken) { |
| mAutosuspendClientTokens.push_back(token); |
| } |
| |
| if (mAutosuspendEnabled) { |
| LOG(ERROR) << "Autosuspend already started."; |
| return false; |
| } |
| |
| mAutosuspendEnabled = true; |
| initAutosuspendLocked(); |
| return true; |
| } |
| |
| void SystemSuspend::disableAutosuspendLocked() { |
| mAutosuspendClientTokens.clear(); |
| if (mAutosuspendEnabled) { |
| mAutosuspendEnabled = false; |
| mAutosuspendCondVar.notify_all(); |
| LOG(INFO) << "automatic system suspend disabled"; |
| } |
| } |
| |
| void SystemSuspend::disableAutosuspend() { |
| auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock); |
| auto autosuspendLock = std::lock_guard(mAutosuspendLock); |
| disableAutosuspendLocked(); |
| } |
| |
| void SystemSuspend::checkAutosuspendClientsLivenessLocked() { |
| // Ping autosuspend client tokens, remove any dead tokens from the list. |
| // mAutosuspendLock must not be held when calling this, as that could lead to a deadlock |
| // if pingBinder() can't be processed by system_server because it's Binder thread pool is |
| // exhausted and blocked on acquire/release wakelock calls. |
| mAutosuspendClientTokens.erase( |
| std::remove_if(mAutosuspendClientTokens.begin(), mAutosuspendClientTokens.end(), |
| [](const sp<IBinder>& token) { return token->pingBinder() != OK; }), |
| mAutosuspendClientTokens.end()); |
| } |
| |
| bool SystemSuspend::hasAliveAutosuspendTokenLocked() { |
| return !mAutosuspendClientTokens.empty(); |
| } |
| |
| SystemSuspend::~SystemSuspend(void) { |
| auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock); |
| auto autosuspendLock = std::unique_lock(mAutosuspendLock); |
| |
| // signal autosuspend thread to shut down |
| disableAutosuspendLocked(); |
| |
| // wait for autosuspend thread to exit |
| mAutosuspendCondVar.wait_for(autosuspendLock, 100ms, [this]() REQUIRES(mAutosuspendLock) { |
| return !mAutosuspendThreadCreated; |
| }); |
| } |
| |
| bool SystemSuspend::forceSuspend() { |
| // We are forcing the system to suspend. This particular call ignores all |
| // existing wakelocks (full or partial). It does not cancel the wakelocks |
| // or reset mSuspendCounter, it just ignores them. When the system |
| // returns from suspend, the wakelocks and SuspendCounter will not have |
| // changed. |
| auto autosuspendLock = std::unique_lock(mAutosuspendLock); |
| bool success = WriteStringToFd(kSleepState, mStateFd); |
| autosuspendLock.unlock(); |
| |
| if (!success) { |
| PLOG(VERBOSE) << "error writing to /sys/power/state for forceSuspend"; |
| } |
| return success; |
| } |
| |
| void SystemSuspend::incSuspendCounter(const string& name) { |
| auto l = std::lock_guard(mAutosuspendLock); |
| if (mUseSuspendCounter) { |
| mSuspendCounter++; |
| } else { |
| if (!WriteStringToFd(name, mWakeLockFd)) { |
| PLOG(ERROR) << "error writing " << name << " to " << kSysPowerWakeLock; |
| } |
| } |
| } |
| |
| void SystemSuspend::decSuspendCounter(const string& name) { |
| auto l = std::lock_guard(mAutosuspendLock); |
| if (mUseSuspendCounter) { |
| if (--mSuspendCounter == 0) { |
| mAutosuspendCondVar.notify_one(); |
| } |
| } else { |
| if (!WriteStringToFd(name, mWakeUnlockFd)) { |
| PLOG(ERROR) << "error writing " << name << " to " << kSysPowerWakeUnlock; |
| } |
| } |
| } |
| |
| unique_fd SystemSuspend::reopenFileUsingFd(const int fd, const int permission) { |
| string filePath = android::base::StringPrintf("/proc/self/fd/%d", fd); |
| |
| unique_fd tempFd{TEMP_FAILURE_RETRY(open(filePath.c_str(), permission))}; |
| if (tempFd < 0) { |
| PLOG(ERROR) << "SystemSuspend: Error opening file, using path: " << filePath; |
| return unique_fd(-1); |
| } |
| return tempFd; |
| } |
| |
| void SystemSuspend::initAutosuspendLocked() { |
| if (mAutosuspendThreadCreated) { |
| LOG(INFO) << "Autosuspend thread already started."; |
| return; |
| } |
| |
| std::thread autosuspendThread([this] { |
| auto autosuspendLock = std::unique_lock(mAutosuspendLock); |
| bool shouldSleep = true; |
| |
| while (true) { |
| { |
| base::ScopedLockAssertion autosuspendLocked(mAutosuspendLock); |
| |
| if (!mAutosuspendEnabled) { |
| mAutosuspendThreadCreated = false; |
| return; |
| } |
| // If we got here by a failed write to /sys/power/wakeup_count; don't sleep |
| // since we didn't attempt to suspend on the last cycle of this loop. |
| if (shouldSleep) { |
| mAutosuspendCondVar.wait_for( |
| autosuspendLock, mSleepTime, |
| [this]() REQUIRES(mAutosuspendLock) { return !mAutosuspendEnabled; }); |
| } |
| |
| if (!mAutosuspendEnabled) continue; |
| autosuspendLock.unlock(); |
| } |
| |
| lseek(mWakeupCountFd, 0, SEEK_SET); |
| string wakeupCount = readFd(mWakeupCountFd); |
| |
| { |
| autosuspendLock.lock(); |
| base::ScopedLockAssertion autosuspendLocked(mAutosuspendLock); |
| |
| if (wakeupCount.empty()) { |
| PLOG(ERROR) << "error reading from /sys/power/wakeup_count"; |
| continue; |
| } |
| |
| shouldSleep = false; |
| |
| mAutosuspendCondVar.wait(autosuspendLock, [this]() REQUIRES(mAutosuspendLock) { |
| return mSuspendCounter == 0 || !mAutosuspendEnabled; |
| }); |
| |
| if (!mAutosuspendEnabled) continue; |
| autosuspendLock.unlock(); |
| } |
| |
| bool success; |
| { |
| auto tokensLock = std::lock_guard(mAutosuspendClientTokensLock); |
| checkAutosuspendClientsLivenessLocked(); |
| |
| autosuspendLock.lock(); |
| base::ScopedLockAssertion autosuspendLocked(mAutosuspendLock); |
| |
| if (!hasAliveAutosuspendTokenLocked()) { |
| disableAutosuspendLocked(); |
| continue; |
| } |
| |
| // Check suspend counter hasn't increased while checking client liveness |
| if (mSuspendCounter > 0) { |
| continue; |
| } |
| |
| // The mutex is locked and *MUST* remain locked until we write to /sys/power/state. |
| // Otherwise, a WakeLock might be acquired after we check mSuspendCounter and before |
| // we write to /sys/power/state. |
| |
| if (!WriteStringToFd(wakeupCount, mWakeupCountFd)) { |
| PLOG(VERBOSE) << "error writing to /sys/power/wakeup_count"; |
| continue; |
| } |
| success = WriteStringToFd(kSleepState, mStateFd); |
| shouldSleep = true; |
| |
| autosuspendLock.unlock(); |
| } |
| |
| if (!success) { |
| PLOG(VERBOSE) << "error writing to /sys/power/state"; |
| } |
| |
| struct SuspendTime suspendTime = readSuspendTime(mSuspendTimeFd); |
| updateSleepTime(success, suspendTime); |
| |
| std::vector<std::string> wakeupReasons = readWakeupReasons(mWakeupReasonsFd); |
| if (wakeupReasons == std::vector<std::string>({kUnknownWakeup})) { |
| LOG(INFO) << "Unknown/empty wakeup reason. Re-opening wakeup_reason file."; |
| |
| mWakeupReasonsFd = |
| std::move(reopenFileUsingFd(mWakeupReasonsFd.get(), O_CLOEXEC | O_RDONLY)); |
| } |
| mWakeupList.update(wakeupReasons); |
| |
| mControlService->notifyWakeup(success, wakeupReasons); |
| |
| // Take the lock before returning to the start of the loop |
| autosuspendLock.lock(); |
| } |
| }); |
| autosuspendThread.detach(); |
| mAutosuspendThreadCreated = true; |
| LOG(INFO) << "automatic system suspend enabled"; |
| } |
| |
| /** |
| * Updates sleep time depending on the result of suspend attempt. |
| * Time (in milliseconds) between suspend attempts is described the formula |
| * t[n] = { B, 0 < n <= N |
| * { min(B * (S**(n - N)), M), n > N |
| * where: |
| * n is the number of consecutive bad suspend attempts, |
| * B = kBaseSleepTime, |
| * N = kSuspendBackoffThreshold, |
| * S = kSleepTimeScaleFactor, |
| * M = kMaxSleepTime |
| * |
| * kFailedSuspendBackoffEnabled determines whether a failed suspend is counted as a bad suspend |
| * |
| * kShortSuspendBackoffEnabled determines whether a suspend whose duration |
| * t < kShortSuspendThreshold is counted as a bad suspend |
| */ |
| void SystemSuspend::updateSleepTime(bool success, const struct SuspendTime& suspendTime) { |
| std::scoped_lock lock(mSuspendInfoLock); |
| mSuspendInfo.suspendAttemptCount++; |
| mSuspendInfo.sleepTimeMillis += |
| std::chrono::round<std::chrono::milliseconds>(mSleepTime).count(); |
| |
| bool shortSuspend = success && (suspendTime.suspendTime > 0ns) && |
| (suspendTime.suspendTime < kSleepTimeConfig.shortSuspendThreshold); |
| |
| bool badSuspend = (kSleepTimeConfig.failedSuspendBackoffEnabled && !success) || |
| (kSleepTimeConfig.shortSuspendBackoffEnabled && shortSuspend); |
| |
| auto suspendTimeMillis = |
| std::chrono::round<std::chrono::milliseconds>(suspendTime.suspendTime).count(); |
| auto suspendOverheadMillis = |
| std::chrono::round<std::chrono::milliseconds>(suspendTime.suspendOverhead).count(); |
| |
| if (success) { |
| mSuspendInfo.suspendOverheadTimeMillis += suspendOverheadMillis; |
| mSuspendInfo.suspendTimeMillis += suspendTimeMillis; |
| } else { |
| mSuspendInfo.failedSuspendCount++; |
| mSuspendInfo.failedSuspendOverheadTimeMillis += suspendOverheadMillis; |
| } |
| |
| if (shortSuspend) { |
| mSuspendInfo.shortSuspendCount++; |
| mSuspendInfo.shortSuspendTimeMillis += suspendTimeMillis; |
| } |
| |
| if (!badSuspend) { |
| mNumConsecutiveBadSuspends = 0; |
| mSleepTime = kSleepTimeConfig.baseSleepTime; |
| return; |
| } |
| |
| // Suspend attempt was bad (failed or short suspend) |
| if (mNumConsecutiveBadSuspends >= kSleepTimeConfig.backoffThreshold) { |
| if (mNumConsecutiveBadSuspends == kSleepTimeConfig.backoffThreshold) { |
| mSuspendInfo.newBackoffCount++; |
| } else { |
| mSuspendInfo.backoffContinueCount++; |
| } |
| |
| mSleepTime = std::min(std::chrono::round<std::chrono::milliseconds>( |
| mSleepTime * kSleepTimeConfig.sleepTimeScaleFactor), |
| kSleepTimeConfig.maxSleepTime); |
| } |
| |
| mNumConsecutiveBadSuspends++; |
| } |
| |
| void SystemSuspend::updateWakeLockStatOnAcquire(const std::string& name, int pid) { |
| // Update the stats first so that the stat time is right after |
| // suspend counter being incremented. |
| mStatsList.updateOnAcquire(name, pid); |
| mControlService->notifyWakelock(name, true); |
| } |
| |
| void SystemSuspend::updateWakeLockStatOnRelease(const std::string& name, int pid) { |
| // Update the stats first so that the stat time is right after |
| // suspend counter being decremented. |
| mStatsList.updateOnRelease(name, pid); |
| mControlService->notifyWakelock(name, false); |
| } |
| |
| const WakeLockEntryList& SystemSuspend::getStatsList() const { |
| return mStatsList; |
| } |
| |
| void SystemSuspend::updateStatsNow() { |
| mStatsList.updateNow(); |
| } |
| |
| void SystemSuspend::getSuspendInfo(SuspendInfo* info) { |
| std::scoped_lock lock(mSuspendInfoLock); |
| |
| *info = mSuspendInfo; |
| } |
| |
| const WakeupList& SystemSuspend::getWakeupList() const { |
| return mWakeupList; |
| } |
| |
| /** |
| * Returns suspend stats. |
| */ |
| Result<SuspendStats> SystemSuspend::getSuspendStats() { |
| SuspendStats stats; |
| std::unique_ptr<DIR, decltype(&closedir)> dp(fdopendir(dup(mSuspendStatsFd.get())), &closedir); |
| if (!dp) { |
| return stats; |
| } |
| |
| // rewinddir, else subsequent calls will not get any suspend_stats |
| rewinddir(dp.get()); |
| |
| struct dirent* de; |
| |
| // Grab a wakelock before reading suspend stats, to ensure a consistent snapshot. |
| const std::string suspendInstance = std::string() + ISystemSuspend::descriptor + "/default"; |
| auto suspendService = ISystemSuspend::fromBinder( |
| ndk::SpAIBinder(AServiceManager_checkService(suspendInstance.c_str()))); |
| |
| std::shared_ptr<IWakeLock> wl = nullptr; |
| if (suspendService) { |
| auto status = |
| suspendService->acquireWakeLock(WakeLockType::PARTIAL, "suspend_stats_lock", &wl); |
| } |
| |
| while ((de = readdir(dp.get()))) { |
| std::string statName(de->d_name); |
| if ((statName == ".") || (statName == "..")) { |
| continue; |
| } |
| |
| unique_fd statFd{TEMP_FAILURE_RETRY( |
| openat(mSuspendStatsFd.get(), statName.c_str(), O_CLOEXEC | O_RDONLY))}; |
| if (statFd < 0) { |
| return Error() << "Failed to open " << statName; |
| } |
| |
| std::string valStr; |
| if (!ReadFdToString(statFd.get(), &valStr)) { |
| return Error() << "Failed to read " << statName; |
| } |
| |
| // Trim newline |
| valStr.erase(std::remove(valStr.begin(), valStr.end(), '\n'), valStr.end()); |
| |
| if (statName == "last_failed_dev") { |
| stats.lastFailedDev = valStr; |
| } else if (statName == "last_failed_step") { |
| stats.lastFailedStep = valStr; |
| } else { |
| int statVal = std::stoi(valStr); |
| if (statName == "success") { |
| stats.success = statVal; |
| } else if (statName == "fail") { |
| stats.fail = statVal; |
| } else if (statName == "failed_freeze") { |
| stats.failedFreeze = statVal; |
| } else if (statName == "failed_prepare") { |
| stats.failedPrepare = statVal; |
| } else if (statName == "failed_suspend") { |
| stats.failedSuspend = statVal; |
| } else if (statName == "failed_suspend_late") { |
| stats.failedSuspendLate = statVal; |
| } else if (statName == "failed_suspend_noirq") { |
| stats.failedSuspendNoirq = statVal; |
| } else if (statName == "failed_resume") { |
| stats.failedResume = statVal; |
| } else if (statName == "failed_resume_early") { |
| stats.failedResumeEarly = statVal; |
| } else if (statName == "failed_resume_noirq") { |
| stats.failedResumeNoirq = statVal; |
| } else if (statName == "last_failed_errno") { |
| stats.lastFailedErrno = statVal; |
| } |
| } |
| } |
| |
| return stats; |
| } |
| |
| std::chrono::milliseconds SystemSuspend::getSleepTime() const { |
| return mSleepTime; |
| } |
| |
| } // namespace V1_0 |
| } // namespace suspend |
| } // namespace system |
| } // namespace android |