--- /dev/null
+// 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.
+
+cc_defaults {
+ name: "system_suspend_defaults",
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libhidlbase",
+ "libhidltransport",
+ "libhwbinder",
+ "liblog",
+ "libutils",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ cpp_std: "c++17",
+}
+
+cc_binary {
+ name: "android.system.suspend@1.0-service",
+ relative_install_path: "hw",
+ defaults: [
+ "system_suspend_defaults",
+ ],
+ init_rc: ["android.system.suspend@1.0-service.rc"],
+ vintf_fragments: ["android.system.suspend@1.0-service.xml"],
+ shared_libs: ["android.system.suspend@1.0"],
+ srcs: [
+ "SystemSuspend.cpp",
+ "main.cpp",
+ ],
+}
+
+// Unit tests for ISystemSuspend implementation.
+// Do *NOT* use for compliance with *TS.
+cc_test {
+ name: "SystemSuspendV1_0UnitTest",
+ defaults: ["system_suspend_defaults"],
+ static_libs: ["android.system.suspend@1.0"],
+ srcs: [
+ "SystemSuspend.cpp",
+ "SystemSuspendUnitTest.cpp"
+ ],
+}
+
--- /dev/null
+/*
+ * 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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <hidl/Status.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <string>
+#include <thread>
+
+using ::android::base::ReadFdToString;
+using ::android::base::WriteStringToFd;
+using ::android::hardware::Void;
+using ::std::string;
+
+namespace android {
+namespace system {
+namespace suspend {
+namespace V1_0 {
+
+static const char kSleepState[] = "mem";
+
+// 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)};
+}
+
+WakeLock::WakeLock(SystemSuspend* systemSuspend) : mSystemSuspend(systemSuspend) {
+ mSystemSuspend->incSuspendCounter();
+}
+
+WakeLock::~WakeLock() {
+ mSystemSuspend->decSuspendCounter();
+}
+
+SystemSuspend::SystemSuspend(unique_fd wakeupCountFd, unique_fd stateFd)
+ : mCounterLock(),
+ mCounterCondVar(),
+ mSuspendCounter(0),
+ mWakeupCountFd(std::move(wakeupCountFd)),
+ mStateFd(std::move(stateFd)) {}
+
+Return<bool> SystemSuspend::enableAutosuspend() {
+ static bool initialized = false;
+ if (initialized) {
+ LOG(ERROR) << "Autosuspend already started.";
+ return false;
+ }
+
+ initAutosuspend();
+ initialized = true;
+ return true;
+}
+
+Return<sp<IWakeLock>> SystemSuspend::acquireWakeLock() {
+ return new WakeLock{this};
+}
+
+Return<void> SystemSuspend::debug(const hidl_handle& handle,
+ const hidl_vec<hidl_string>& /* options */) {
+ if (handle == nullptr || handle->numFds < 1 || handle->data[0] < 0) {
+ LOG(ERROR) << "no valid fd";
+ return Void();
+ }
+ int fd = handle->data[0];
+ WriteStringToFd(std::to_string(mSuspendCounter) + "\n", fd);
+ fsync(fd);
+ return Void();
+}
+
+void SystemSuspend::incSuspendCounter() {
+ auto l = std::lock_guard(mCounterLock);
+ mSuspendCounter++;
+}
+
+void SystemSuspend::decSuspendCounter() {
+ auto l = std::lock_guard(mCounterLock);
+ if (--mSuspendCounter == 0) {
+ mCounterCondVar.notify_one();
+ }
+}
+
+void SystemSuspend::initAutosuspend() {
+ std::thread autosuspendThread([this] {
+ while (true) {
+ lseek(mWakeupCountFd, 0, SEEK_SET);
+ const string wakeupCount = readFd(mWakeupCountFd);
+ if (wakeupCount.empty()) {
+ PLOG(ERROR) << "error reading from /sys/power/wakeup_count";
+ continue;
+ }
+
+ auto l = std::unique_lock(mCounterLock);
+ mCounterCondVar.wait(l, [this] { return mSuspendCounter == 0; });
+ // The mutex is locked and *MUST* remain locked until the end of the scope. 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 from /sys/power/wakeup_count";
+ continue;
+ }
+ if (!WriteStringToFd(kSleepState, mStateFd)) {
+ PLOG(VERBOSE) << "error writing to /sys/power/state";
+ }
+ }
+ });
+ autosuspendThread.detach();
+ LOG(INFO) << "automatic system suspend enabled";
+}
+
+} // namespace V1_0
+} // namespace suspend
+} // namespace system
+} // namespace android
--- /dev/null
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_SYSTEM_SYSTEM_SUSPEND_V1_0_H
+#define ANDROID_SYSTEM_SYSTEM_SUSPEND_V1_0_H
+
+#include <android-base/unique_fd.h>
+#include <android/system/suspend/1.0/ISystemSuspend.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <string>
+
+namespace android {
+namespace system {
+namespace suspend {
+namespace V1_0 {
+
+using ::android::base::unique_fd;
+using ::android::hardware::hidl_handle;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::Return;
+
+class SystemSuspend;
+
+std::string readFd(int fd);
+
+class WakeLock : public IWakeLock {
+ public:
+ WakeLock(SystemSuspend* systemSuspend);
+ ~WakeLock();
+
+ private:
+ SystemSuspend* mSystemSuspend;
+};
+
+class SystemSuspend : public ISystemSuspend {
+ public:
+ SystemSuspend(unique_fd wakeupCountFd, unique_fd stateFd);
+ Return<bool> enableAutosuspend() override;
+ Return<sp<IWakeLock>> acquireWakeLock() override;
+ Return<void> debug(const hidl_handle& handle, const hidl_vec<hidl_string>& options) override;
+ void incSuspendCounter();
+ void decSuspendCounter();
+
+ private:
+ void initAutosuspend();
+
+ std::mutex mCounterLock;
+ std::condition_variable mCounterCondVar;
+ uint32_t mSuspendCounter;
+ unique_fd mWakeupCountFd;
+ unique_fd mStateFd;
+};
+
+} // namespace V1_0
+} // namespace suspend
+} // namespace system
+} // namespace android
+
+#endif // ANDROID_SYSTEM_SYSTEM_SUSPEND_V1_0_H
--- /dev/null
+/*
+ * 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 <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <cutils/native_handle.h>
+#include <gtest/gtest.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <chrono>
+#include <csignal>
+#include <cstdlib>
+#include <future>
+#include <string>
+#include <thread>
+
+using android::sp;
+using android::base::Socketpair;
+using android::base::unique_fd;
+using android::base::WriteStringToFd;
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+using android::hardware::Return;
+using android::hardware::Void;
+using android::system::suspend::V1_0::ISystemSuspend;
+using android::system::suspend::V1_0::IWakeLock;
+using android::system::suspend::V1_0::readFd;
+using android::system::suspend::V1_0::SystemSuspend;
+
+namespace android {
+
+static constexpr char kServiceName[] = "TestService";
+
+static bool isReadBlocked(int fd) {
+ struct pollfd pfd {
+ .fd = fd, .events = POLLIN,
+ };
+ int timeout_ms = 20;
+ return poll(&pfd, 1, timeout_ms) == 0;
+}
+
+class SystemSuspendTestEnvironment : public ::testing::Environment {
+ public:
+ using Env = SystemSuspendTestEnvironment;
+ static Env* Instance() {
+ static Env* instance = new Env{};
+ return instance;
+ }
+
+ SystemSuspendTestEnvironment() {
+ Socketpair(SOCK_STREAM, &wakeupCountFds[0], &wakeupCountFds[1]);
+ Socketpair(SOCK_STREAM, &stateFds[0], &stateFds[1]);
+ }
+
+ void registerTestService() {
+ std::thread testService([this] {
+ configureRpcThreadpool(1, true /* callerWillJoin */);
+ sp<ISystemSuspend> suspend =
+ new SystemSuspend(std::move(wakeupCountFds[1]), std::move(stateFds[1]));
+ status_t status = suspend->registerAsService(kServiceName);
+ if (android::OK != status) {
+ LOG(FATAL) << "Unable to register service: " << status;
+ }
+ joinRpcThreadpool();
+ });
+ testService.detach();
+ }
+
+ virtual void SetUp() {
+ registerTestService();
+ ::android::hardware::details::waitForHwService(ISystemSuspend::descriptor, kServiceName);
+ sp<ISystemSuspend> suspendService = ISystemSuspend::getService(kServiceName);
+ ASSERT_NE(suspendService, nullptr) << "failed to get suspend service";
+ ASSERT_EQ(suspendService->enableAutosuspend(), true) << "failed to start autosuspend";
+ }
+
+ unique_fd wakeupCountFds[2];
+ unique_fd stateFds[2];
+};
+
+class SystemSuspendTest : public ::testing::Test {
+ public:
+ virtual void SetUp() override {
+ ::android::hardware::details::waitForHwService(ISystemSuspend::descriptor, kServiceName);
+ suspendService = ISystemSuspend::getService(kServiceName);
+ ASSERT_NE(suspendService, nullptr) << "failed to get suspend service";
+
+ auto* environment = SystemSuspendTestEnvironment::Instance();
+ wakeupCountFd = environment->wakeupCountFds[0];
+ stateFd = environment->stateFds[0];
+
+ // SystemSuspend HAL should not have written back to wakeupCountFd or stateFd yet.
+ ASSERT_TRUE(isReadBlocked(wakeupCountFd));
+ ASSERT_TRUE(isReadBlocked(stateFd));
+ }
+
+ virtual void TearDown() override {
+ if (!isReadBlocked(wakeupCountFd)) readFd(wakeupCountFd);
+ if (!isReadBlocked(stateFd)) readFd(stateFd).empty();
+ ASSERT_TRUE(isReadBlocked(wakeupCountFd));
+ ASSERT_TRUE(isReadBlocked(stateFd));
+ }
+
+ void unblockSystemSuspendFromWakeupCount() {
+ std::string wakeupCount = std::to_string(rand());
+ ASSERT_TRUE(WriteStringToFd(wakeupCount, wakeupCountFd));
+ }
+
+ bool isSystemSuspendBlocked() { return isReadBlocked(stateFd); }
+
+ sp<ISystemSuspend> suspendService;
+ int stateFd;
+ int wakeupCountFd;
+};
+
+// Tests that autosuspend thread can only be enabled once.
+TEST_F(SystemSuspendTest, OnlyOneEnableAutosuspend) {
+ ASSERT_EQ(suspendService->enableAutosuspend(), false);
+}
+
+TEST_F(SystemSuspendTest, AutosuspendLoop) {
+ for (int i = 0; i < 2; i++) {
+ // Mock value for /sys/power/wakeup_count.
+ std::string wakeupCount = std::to_string(rand());
+ ASSERT_TRUE(WriteStringToFd(wakeupCount, wakeupCountFd));
+ ASSERT_EQ(readFd(wakeupCountFd), wakeupCount)
+ << "wakeup count value written by SystemSuspend is not equal to value given to it";
+ ASSERT_EQ(readFd(stateFd), "mem") << "SystemSuspend failed to write correct sleep state.";
+ }
+}
+
+// Tests that upon WakeLock destruction SystemSuspend HAL is unblocked.
+TEST_F(SystemSuspendTest, WakeLockDestructor) {
+ {
+ sp<IWakeLock> wl = suspendService->acquireWakeLock();
+ ASSERT_NE(wl, nullptr);
+ unblockSystemSuspendFromWakeupCount();
+ ASSERT_TRUE(isSystemSuspendBlocked());
+ }
+ ASSERT_FALSE(isSystemSuspendBlocked());
+}
+
+// Tests that multiple WakeLocks correctly block SystemSuspend HAL.
+TEST_F(SystemSuspendTest, MultipleWakeLocks) {
+ {
+ sp<IWakeLock> wl1 = suspendService->acquireWakeLock();
+ ASSERT_NE(wl1, nullptr);
+ ASSERT_TRUE(isSystemSuspendBlocked());
+ unblockSystemSuspendFromWakeupCount();
+ {
+ sp<IWakeLock> wl2 = suspendService->acquireWakeLock();
+ ASSERT_NE(wl2, nullptr);
+ ASSERT_TRUE(isSystemSuspendBlocked());
+ }
+ ASSERT_TRUE(isSystemSuspendBlocked());
+ }
+ ASSERT_FALSE(isSystemSuspendBlocked());
+}
+
+// Tests that upon thread deallocation WakeLock is destructed and SystemSuspend HAL is unblocked.
+TEST_F(SystemSuspendTest, ThreadCleanup) {
+ std::thread clientThread([this] {
+ sp<IWakeLock> wl = suspendService->acquireWakeLock();
+ ASSERT_NE(wl, nullptr);
+ unblockSystemSuspendFromWakeupCount();
+ ASSERT_TRUE(isSystemSuspendBlocked());
+ });
+ clientThread.join();
+ ASSERT_FALSE(isSystemSuspendBlocked());
+}
+
+// Test that binder driver correctly deallocates acquired WakeLocks, even if the client processs
+// is terminated without ability to do clean up.
+TEST_F(SystemSuspendTest, CleanupOnAbort) {
+ ASSERT_EXIT(
+ {
+ sp<IWakeLock> wl = suspendService->acquireWakeLock();
+ ASSERT_NE(wl, nullptr);
+ std::abort();
+ },
+ ::testing::KilledBySignal(SIGABRT), "");
+ ASSERT_TRUE(isSystemSuspendBlocked());
+ unblockSystemSuspendFromWakeupCount();
+ ASSERT_FALSE(isSystemSuspendBlocked());
+}
+
+} // namespace android
+
+int main(int argc, char** argv) {
+ setenv("TREBLE_TESTING_OVERRIDE", "true", true);
+ ::testing::AddGlobalTestEnvironment(android::SystemSuspendTestEnvironment::Instance());
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
--- /dev/null
+service system_suspend /system/bin/hw/android.system.suspend@1.0-service
+ class hal
+ user system
+ group system
--- /dev/null
+<manifest version="1.0" type="framework">
+ <hal>
+ <name>android.system.suspend</name>
+ <transport>hwbinder</transport>
+ <version>1.0</version>
+ <interface>
+ <name>ISystemSuspend</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
--- /dev/null
+#include "SystemSuspend.h"
+
+#include <android-base/logging.h>
+#include <cutils/native_handle.h>
+#include <hidl/HidlTransportSupport.h>
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+
+using android::sp;
+using android::status_t;
+using android::base::unique_fd;
+using android::hardware::configureRpcThreadpool;
+using android::hardware::joinRpcThreadpool;
+using android::system::suspend::V1_0::ISystemSuspend;
+using android::system::suspend::V1_0::SystemSuspend;
+
+static constexpr char kSysPowerWakeupCount[] = "/sys/power/wakeup_count";
+static constexpr char kSysPowerState[] = "/sys/power/state";
+
+int main() {
+ unique_fd wakeupCountFd{TEMP_FAILURE_RETRY(open(kSysPowerWakeupCount, O_CLOEXEC | O_RDWR))};
+ if (wakeupCountFd < 0) {
+ PLOG(ERROR) << "error opening " << kSysPowerWakeupCount;
+ return 1;
+ }
+ unique_fd stateFd{TEMP_FAILURE_RETRY(open(kSysPowerState, O_CLOEXEC | O_RDWR))};
+ if (stateFd < 0) {
+ PLOG(ERROR) << "error opening " << kSysPowerState;
+ return 1;
+ }
+
+ configureRpcThreadpool(1, true /* callerWillJoin */);
+ sp<ISystemSuspend> suspend = new SystemSuspend(std::move(wakeupCountFd), std::move(stateFd));
+ status_t status = suspend->registerAsService();
+ if (android::OK != status) {
+ LOG(FATAL) << "Unable to register service: " << status;
+ }
+ joinRpcThreadpool();
+ std::abort(); /* unreachable */
+}