OSDN Git Service

System suspend HAL implementation.
authorTri Vo <trong@google.com>
Thu, 16 Aug 2018 04:20:08 +0000 (21:20 -0700)
committerTri Vo <trong@google.com>
Thu, 16 Aug 2018 04:20:08 +0000 (21:20 -0700)
Bug: 78888165
Test: SystemSuspendV1_0UnitTest
Change-Id: I4ba1075ce51684e431098635c72aece67351644a

suspend/1.0/default/Android.bp [new file with mode: 0644]
suspend/1.0/default/SystemSuspend.cpp [new file with mode: 0644]
suspend/1.0/default/SystemSuspend.h [new file with mode: 0644]
suspend/1.0/default/SystemSuspendUnitTest.cpp [new file with mode: 0644]
suspend/1.0/default/android.system.suspend@1.0-service.rc [new file with mode: 0644]
suspend/1.0/default/android.system.suspend@1.0-service.xml [new file with mode: 0644]
suspend/1.0/default/main.cpp [new file with mode: 0644]

diff --git a/suspend/1.0/default/Android.bp b/suspend/1.0/default/Android.bp
new file mode 100644 (file)
index 0000000..7959f4f
--- /dev/null
@@ -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.
+
+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"
+    ],
+}
+
diff --git a/suspend/1.0/default/SystemSuspend.cpp b/suspend/1.0/default/SystemSuspend.cpp
new file mode 100644 (file)
index 0000000..ba3dbeb
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * 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
diff --git a/suspend/1.0/default/SystemSuspend.h b/suspend/1.0/default/SystemSuspend.h
new file mode 100644 (file)
index 0000000..a488bd0
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * 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
diff --git a/suspend/1.0/default/SystemSuspendUnitTest.cpp b/suspend/1.0/default/SystemSuspendUnitTest.cpp
new file mode 100644 (file)
index 0000000..7d317e8
--- /dev/null
@@ -0,0 +1,214 @@
+/*
+ * 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();
+}
diff --git a/suspend/1.0/default/android.system.suspend@1.0-service.rc b/suspend/1.0/default/android.system.suspend@1.0-service.rc
new file mode 100644 (file)
index 0000000..4a4b46a
--- /dev/null
@@ -0,0 +1,4 @@
+service system_suspend /system/bin/hw/android.system.suspend@1.0-service
+    class hal
+    user system
+    group system
diff --git a/suspend/1.0/default/android.system.suspend@1.0-service.xml b/suspend/1.0/default/android.system.suspend@1.0-service.xml
new file mode 100644 (file)
index 0000000..9f06ae3
--- /dev/null
@@ -0,0 +1,11 @@
+<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>
diff --git a/suspend/1.0/default/main.cpp b/suspend/1.0/default/main.cpp
new file mode 100644 (file)
index 0000000..24f0af4
--- /dev/null
@@ -0,0 +1,44 @@
+#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 */
+}